0001: /*****************************************************************************
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is the CVS Client Library.
0027: * The Initial Developer of the Original Software is Robert Greig.
0028: * Portions created by Robert Greig are Copyright (C) 2000.
0029: * All Rights Reserved.
0030: *
0031: * If you wish your version of this file to be governed by only the CDDL
0032: * or only the GPL Version 2, indicate your decision by adding
0033: * "[Contributor] elects to include this software in this distribution
0034: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0035: * single choice of license, a recipient has the option to distribute
0036: * your version of this file under either the CDDL, the GPL Version 2 or
0037: * to extend the choice of license to its licensees as provided above.
0038: * However, if you add GPL Version 2 code and therefore, elected the GPL
0039: * Version 2 license, then the option applies only if the new code is
0040: * made subject to such option by the copyright holder.
0041: *
0042: * Contributor(s): Robert Greig.
0043: *****************************************************************************/package org.netbeans.lib.cvsclient;
0044:
0045: import java.io.*;
0046: import java.util.*;
0047:
0048: import org.netbeans.lib.cvsclient.admin.*;
0049: import org.netbeans.lib.cvsclient.command.*;
0050: import org.netbeans.lib.cvsclient.connection.*;
0051: import org.netbeans.lib.cvsclient.event.*;
0052: import org.netbeans.lib.cvsclient.file.*;
0053: import org.netbeans.lib.cvsclient.request.*;
0054: import org.netbeans.lib.cvsclient.response.*;
0055: import org.netbeans.lib.cvsclient.util.*;
0056:
0057: /**
0058: * The main way of communication with a server using the CVS Protocol. The
0059: * client is not responsible for setting up the connection with the server,
0060: * only interacting with it.
0061: * @see org.netbeans.lib.cvsclient.connection.Connection
0062: * @author Robert Greig
0063: */
0064: public class Client implements ClientServices, ResponseServices {
0065: /**
0066: * The connection used to interact with the server.
0067: */
0068: private Connection connection;
0069:
0070: /**
0071: * The file handler to use.
0072: */
0073: private FileHandler transmitFileHandler;
0074:
0075: private FileHandler gzipFileHandler = new GzippedFileHandler();
0076: private FileHandler uncompFileHandler = new DefaultFileHandler();
0077:
0078: private boolean dontUseGzipFileHandler;
0079:
0080: /**
0081: * The modified date.
0082: */
0083: private Date modifiedDate;
0084:
0085: /**
0086: * The admin handler to use.
0087: */
0088: private AdminHandler adminHandler;
0089:
0090: /**
0091: * The local path, ie the path to the directory in which this command
0092: * was executed.
0093: */
0094: private String localPath;
0095:
0096: /**
0097: * Whether any commands have been executed so far. This allows us to
0098: * send some initialisation requests before the first command.
0099: */
0100: private boolean isFirstCommand = true;
0101:
0102: /**
0103: * The event manager.
0104: */
0105: private final EventManager eventManager = new EventManager(this );
0106:
0107: /**
0108: * The global options for the executing command.
0109: */
0110: private GlobalOptions globalOptions;
0111:
0112: private PrintStream stderr = System.err;
0113:
0114: /**
0115: * This is set to true if we should abort the current command.
0116: */
0117: private boolean abort;
0118:
0119: private ResponseFactory responseFactory;
0120:
0121: private IgnoreFileFilter ignoreFileFilter;
0122:
0123: /*
0124: * The valid list of requests that is valid for the CVS server
0125: * corresponding to this client
0126: */
0127: private Map validRequests = new HashMap();
0128:
0129: /**
0130: * A map of file patterns and keyword substitution options
0131: */
0132: private Map wrappersMap = null;
0133:
0134: /**
0135: * This will be set to true after initialization requests are sent
0136: * to the server. The initialization requests setup valid requests
0137: * and send RootRequest to the server.
0138: */
0139: private boolean initialRequestsSent = false;
0140:
0141: private boolean printConnectionReuseWarning = false;
0142:
0143: private static final Set ALLOWED_CONNECTION_REUSE_REQUESTS = new HashSet(
0144: Arrays.asList(new Class[] { ExpandModulesRequest.class,
0145: WrapperSendRequest.class }));
0146:
0147: // processRequests & getCounter
0148: private LoggedDataInputStream loggedDataInputStream;
0149: private LoggedDataOutputStream loggedDataOutputStream;
0150: private boolean warned;
0151:
0152: /**
0153: * Construct a Client using a given connection and file handler.
0154: * You must initialize the connection and adminHandler first.
0155: * <code>
0156: * // establish connection to the given CVS pserver
0157: * PServerConnection connection = new PServerConnection();
0158: * connection.setUserName(userName);
0159: * connection.setHostName(hostName);
0160: * connection.setEncodedPassword(StandardScrambler.getInstance().scramble(password));
0161: * connection.setRepository(repository);
0162: * // test the connection
0163: * try {
0164: * connection.open();
0165: * } catch (AuthenticationException e) {
0166: * // do something
0167: * }
0168: *
0169: * // create a CVS client
0170: * Client cvsClient = new Client(connection,new StandardAdminHandler());
0171: *
0172: * // set the directory in which we work
0173: * cvsClient.setLocalPath(localPath);
0174: * </code>
0175: * @param connection the connection to the cvs server
0176: * @param adminHandler the admin handler to use
0177: */
0178: public Client(Connection connection, AdminHandler adminHandler) {
0179: setConnection(connection);
0180: setAdminHandler(adminHandler);
0181: ignoreFileFilter = new DefaultIgnoreFileFilter();
0182: dontUseGzipFileHandler = false;
0183: }
0184:
0185: public void setErrorStream(PrintStream stderr) {
0186: this .stderr = stderr;
0187: }
0188:
0189: /**
0190: * Get the connection used for communicating with the server.
0191: * Connection.
0192: * @return the connection
0193: */
0194: public Connection getConnection() {
0195: return connection;
0196: }
0197:
0198: /**
0199: * Set the connection used for communicating with the server.
0200: * @param c the connection to use for all communication with the server
0201: */
0202: public void setConnection(Connection connection) {
0203: this .connection = connection;
0204: initialRequestsSent = false;
0205: setIsFirstCommand(true);
0206: }
0207:
0208: /**
0209: * Get the admin handler uesd to read and write administrative information
0210: * about files on the local machine.
0211: * @return the admin handler
0212: */
0213: public AdminHandler getAdminHandler() {
0214: return adminHandler;
0215: }
0216:
0217: /**
0218: * Set the admin handler used to read and write administrative information
0219: * about files on the local machine.
0220: */
0221: public void setAdminHandler(AdminHandler adminHandler) {
0222: this .adminHandler = adminHandler;
0223: }
0224:
0225: /**
0226: * Get the local path; that is, the path to the directory in which
0227: * the currently executing command is referring.
0228: */
0229: public String getLocalPath() {
0230: return localPath;
0231: }
0232:
0233: /**
0234: * Set the local path, i.e. the path to the directory in which all
0235: * commands are given (top level).
0236: */
0237: public void setLocalPath(String localPath) {
0238: // remove trailing (back-) slash
0239: localPath = localPath.replace('\\', '/');
0240: while (localPath.endsWith("/")) { // NOI18N
0241: localPath = localPath.substring(0, localPath.length() - 1);
0242: }
0243:
0244: this .localPath = localPath;
0245: }
0246:
0247: /**
0248: * Returns true if no previous command was executed using thiz.
0249: */
0250: public boolean isFirstCommand() {
0251: return isFirstCommand;
0252: }
0253:
0254: /**
0255: * Set whether this is the first command. Normally you do not need to set
0256: * this yourself - after execution the first command will have set this to
0257: * false.
0258: */
0259: public void setIsFirstCommand(boolean isFirstCommand) {
0260: this .isFirstCommand = isFirstCommand;
0261: }
0262:
0263: /**
0264: * Return the uncompressed file handler.
0265: */
0266: public FileHandler getUncompressedFileHandler() {
0267: return uncompFileHandler;
0268: }
0269:
0270: /**
0271: * Set the uncompressed file handler.
0272: */
0273: public void setUncompressedFileHandler(FileHandler uncompFileHandler) {
0274: this .uncompFileHandler = uncompFileHandler;
0275: }
0276:
0277: /**
0278: * Return the Gzip stream handler.
0279: */
0280: public FileHandler getGzipFileHandler() {
0281: return gzipFileHandler;
0282: }
0283:
0284: /**
0285: * Set the handler for Gzip data.
0286: */
0287: public void setGzipFileHandler(FileHandler gzipFileHandler) {
0288: this .gzipFileHandler = gzipFileHandler;
0289: }
0290:
0291: /**
0292: * ReSet the filehandler for Gzip compressed data. Makes sure the
0293: * requests for sending gzipped data are not sent..
0294: */
0295: public void dontUseGzipFileHandler() {
0296: setGzipFileHandler(new DefaultFileHandler());
0297: dontUseGzipFileHandler = true;
0298: }
0299:
0300: public boolean isAborted() {
0301: return abort;
0302: }
0303:
0304: /**
0305: * Ensures, that the connection is open.
0306: *
0307: * @throws AuthenticationException if it wasn't possible to connect
0308: */
0309: public void ensureConnection() throws AuthenticationException {
0310: BugLog.getInstance().assertNotNull(getConnection());
0311:
0312: if (getConnection().isOpen()) {
0313: return;
0314: }
0315:
0316: // #69689 detect silent servers, possibly caused by proxy errors
0317: final Throwable ex[] = new Throwable[1];
0318: final boolean opened[] = new boolean[] { false };
0319: Runnable probe = new Runnable() {
0320: public void run() {
0321: try {
0322: getConnection().open();
0323: synchronized (opened) {
0324: opened[0] = true;
0325: }
0326: } catch (Throwable e) {
0327: synchronized (ex) {
0328: ex[0] = e;
0329: }
0330: }
0331: }
0332: };
0333:
0334: Thread probeThread = new Thread(probe, "CVS Server Probe"); // NOI18N
0335: probeThread.start();
0336: try {
0337:
0338: probeThread.join(60 * 1000); // 1 min
0339:
0340: Throwable wasEx;
0341: synchronized (ex) {
0342: wasEx = ex[0];
0343: }
0344: if (wasEx != null) {
0345: if (wasEx instanceof CommandAbortedException) {
0346: // User cancelled
0347: abort();
0348: return;
0349: } else if (wasEx instanceof AuthenticationException) {
0350: throw (AuthenticationException) wasEx;
0351: } else if (wasEx instanceof RuntimeException) {
0352: throw (RuntimeException) wasEx;
0353: } else if (wasEx instanceof Error) {
0354: throw (Error) wasEx;
0355: } else {
0356: assert false : wasEx;
0357: }
0358: }
0359:
0360: boolean wasOpened;
0361: synchronized (opened) {
0362: wasOpened = opened[0];
0363: }
0364: if (wasOpened == false) {
0365: probeThread.interrupt();
0366: throw new AuthenticationException(
0367: "Timeout, no response from server.",
0368: "Timeout, no response from server.");
0369: }
0370:
0371: } catch (InterruptedException e) {
0372:
0373: // User cancelled
0374: probeThread.interrupt();
0375: abort();
0376: }
0377: }
0378:
0379: /**
0380: * Process all the requests. The connection must have been opened and
0381: * set first.
0382: * @param requests the requets to process
0383: */
0384: public void processRequests(List requests) throws IOException,
0385: UnconfiguredRequestException, ResponseException,
0386: CommandAbortedException {
0387:
0388: if (requests == null || requests.size() == 0) {
0389: throw new IllegalArgumentException(
0390: "[processRequests] requests " + // NOI18N
0391: "was either null or empty."); // NOI18N
0392: }
0393:
0394: if (abort) {
0395: throw new CommandAbortedException(
0396: "Aborted during request processing", // NOI18N
0397: CommandException.getLocalMessage(
0398: "Client.commandAborted", null)); //NOI18N
0399: }
0400:
0401: loggedDataInputStream = null;
0402: loggedDataOutputStream = null;
0403:
0404: // send the initialisation requests if we are handling the first
0405: // command
0406: boolean filterRootRequest = true;
0407: if (isFirstCommand()) {
0408: setIsFirstCommand(false);
0409: int pos = 0;
0410: if (!initialRequestsSent) {
0411: pos = fillInitialRequests(requests);
0412: initialRequestsSent = true;
0413: filterRootRequest = false;
0414: }
0415: if (globalOptions != null) {
0416: // sends the global options that are to be sent to server (-q, -Q, -t, -n, l)
0417: for (Iterator it = globalOptions.createRequestList()
0418: .iterator(); it.hasNext();) {
0419: Request request = (Request) it.next();
0420: requests.add(pos++, request);
0421: }
0422:
0423: if (globalOptions.isUseGzip()
0424: && globalOptions.getCompressionLevel() != 0) {
0425: requests.add(pos++, new GzipFileContentsRequest(
0426: globalOptions.getCompressionLevel()));
0427: }
0428: }
0429: } else if (printConnectionReuseWarning) {
0430: if (System.getProperty("javacvs.multiple_commands_warning") == null) { //NOI18N
0431: System.err.println("WARNING TO DEVELOPERS:"); //NOI18N
0432: System.err
0433: .println("Please be warned that attempting to reuse one open connection for more commands is not supported by cvs servers very well."); //NOI18N
0434: System.err
0435: .println("You are advised to open a new Connection each time."); //NOI18N
0436: System.err
0437: .println("If you still want to proceed, please do: System.setProperty(\"javacvs.multiple_commands_warning\", \"false\")"); //NOI18N
0438: System.err.println("That will disable this message."); //NOI18N
0439: }
0440: }
0441:
0442: if (!ALLOWED_CONNECTION_REUSE_REQUESTS.contains(requests.get(
0443: requests.size() - 1).getClass())) {
0444: printConnectionReuseWarning = true;
0445: }
0446:
0447: final boolean fireEnhancedEvents = getEventManager()
0448: .isFireEnhancedEventSet();
0449: int fileDetailRequestCount = 0;
0450:
0451: if (fireEnhancedEvents) {
0452: for (Iterator it = requests.iterator(); it.hasNext();) {
0453: Request request = (Request) it.next();
0454:
0455: FileDetails fileDetails = request
0456: .getFileForTransmission();
0457: if (fileDetails != null
0458: && fileDetails.getFile().exists()) {
0459: fileDetailRequestCount++;
0460: }
0461: }
0462: CVSEvent event = new EnhancedMessageEvent(this ,
0463: EnhancedMessageEvent.REQUESTS_COUNT, new Integer(
0464: fileDetailRequestCount));
0465: getEventManager().fireCVSEvent(event);
0466: }
0467:
0468: LoggedDataOutputStream dos = connection.getOutputStream();
0469: loggedDataOutputStream = dos;
0470:
0471: // this list stores stream modification requests, each to be called
0472: // to modify the input stream the next time we need to process a
0473: // response
0474: List streamModifierRequests = new LinkedList();
0475:
0476: // sending files does not seem to allow compression
0477: transmitFileHandler = getUncompressedFileHandler();
0478:
0479: for (Iterator it = requests.iterator(); it.hasNext();) {
0480: if (abort) {
0481: throw new CommandAbortedException(
0482: "Aborted during request processing", // NOI18N
0483: CommandException.getLocalMessage(
0484: "Client.commandAborted", null)); //NOI18N
0485: }
0486:
0487: final Request request = (Request) it.next();
0488:
0489: if (request instanceof GzipFileContentsRequest) {
0490: if (dontUseGzipFileHandler) {
0491: stderr
0492: .println("Warning: The server is not supporting gzip-file-contents request, no compression is used.");
0493: continue;
0494: }
0495: }
0496:
0497: // skip the root request if already sent
0498: if (request instanceof RootRequest) {
0499: if (filterRootRequest) {
0500: continue;
0501: } else { // Even if we should not filter the RootRequest now, we must filter all successive RootRequests
0502: filterRootRequest = true;
0503: }
0504: }
0505: // send request to server
0506: String requestString = request.getRequestString();
0507: dos.writeBytes(requestString);
0508:
0509: // we must modify the outputstream now, but defer modification
0510: // of the inputstream until we are about to read a response.
0511: // This is because some modifiers (e.g. gzip) read the header
0512: // on construction, and obviously no header is present when
0513: // no response has been sent
0514: request.modifyOutputStream(connection);
0515: if (request.modifiesInputStream()) {
0516: streamModifierRequests.add(request);
0517: }
0518: dos = connection.getOutputStream();
0519:
0520: FileDetails fileDetails = request.getFileForTransmission();
0521: if (fileDetails != null) {
0522: final File file = fileDetails.getFile();
0523: // only transmit the file if it exists! When committing
0524: // a remove request you cannot transmit the file
0525: if (file.exists()) {
0526: Logger.logOutput(new String("<Sending file: " + // NOI18N
0527: file.getAbsolutePath() + ">\n")
0528: .getBytes("utf8")); // NOI18N
0529:
0530: if (fireEnhancedEvents) {
0531: CVSEvent event = new EnhancedMessageEvent(this ,
0532: EnhancedMessageEvent.FILE_SENDING, file);
0533: getEventManager().fireCVSEvent(event);
0534:
0535: fileDetailRequestCount--;
0536: }
0537:
0538: if (fileDetails.isBinary()) {
0539: transmitFileHandler.transmitBinaryFile(file,
0540: dos);
0541: } else {
0542: transmitFileHandler.transmitTextFile(file, dos);
0543: }
0544:
0545: if (fireEnhancedEvents
0546: && fileDetailRequestCount == 0) {
0547: CVSEvent event = new EnhancedMessageEvent(this ,
0548: EnhancedMessageEvent.REQUESTS_SENT,
0549: "Ok"); // NOI18N
0550: getEventManager().fireCVSEvent(event);
0551: }
0552: }
0553: }
0554: if (request.isResponseExpected()) {
0555: dos.flush();
0556:
0557: // now perform the deferred modification of the input stream
0558: Iterator modifiers = streamModifierRequests.iterator();
0559: while (modifiers.hasNext()) {
0560: System.err.println("Modifying the inputstream..."); // NOI18N
0561: final Request smRequest = (Request) modifiers
0562: .next();
0563: System.err.println("Request is a: " + // NOI18N
0564: smRequest.getClass().getName());
0565: smRequest.modifyInputStream(connection);
0566: }
0567: streamModifierRequests.clear();
0568:
0569: handleResponse();
0570: }
0571: }
0572: dos.flush();
0573:
0574: transmitFileHandler = null;
0575: }
0576:
0577: private ResponseFactory getResponseFactory() {
0578: if (responseFactory == null) {
0579: responseFactory = new ResponseFactory();
0580: }
0581: return responseFactory;
0582: }
0583:
0584: /**
0585: * Handle the response from a request.
0586: * @throws ResponseException if there is a problem reading the response
0587: */
0588: private void handleResponse() throws ResponseException,
0589: CommandAbortedException {
0590: try {
0591: LoggedDataInputStream dis = connection.getInputStream();
0592: loggedDataInputStream = dis;
0593:
0594: int ch = -1;
0595: try {
0596: ch = dis.read();
0597: } catch (InterruptedIOException ex) {
0598: abort();
0599: }
0600:
0601: while (!abort && ch != -1) {
0602: StringBuffer responseNameBuffer = new StringBuffer();
0603: // read in the response name
0604: while (ch != -1 && (char) ch != '\n'
0605: && (char) ch != ' ') {
0606: responseNameBuffer.append((char) ch);
0607: try {
0608: ch = dis.read();
0609: } catch (InterruptedIOException ex) {
0610: abort();
0611: break;
0612: }
0613: }
0614:
0615: String responseString = responseNameBuffer.toString();
0616: Response response = getResponseFactory()
0617: .createResponse(responseString);
0618: //Logger.logInput(new String("<" + responseString + " processing start>\n").getBytes()); // NOI18N
0619: response.process(dis, this );
0620: boolean terminal = response.isTerminalResponse();
0621:
0622: // handle SpecialResponses
0623: if (terminal
0624: && response instanceof ErrorMessageResponse) {
0625: ErrorMessageResponse errorResponce = (ErrorMessageResponse) response;
0626: String errMsg = errorResponce.getMessage();
0627: throw new CommandAbortedException(errMsg, errMsg);
0628: }
0629: //Logger.logInput(new String("<" + responseString + " processed " + terminal + ">\n").getBytes()); // NOI18N
0630: if (terminal || abort) {
0631: break;
0632: }
0633:
0634: try {
0635: ch = dis.read();
0636: } catch (InterruptedIOException ex) {
0637: abort();
0638: break;
0639: }
0640: }
0641:
0642: if (abort) {
0643: String localMsg = CommandException.getLocalMessage(
0644: "Client.commandAborted", null); //NOI18N
0645: throw new CommandAbortedException(
0646: "Aborted during request processing", localMsg); // NOI18N
0647: }
0648: } catch (EOFException ex) {
0649: throw new ResponseException(ex,
0650: ResponseException.getLocalMessage(
0651: "CommandException.EndOfFile", null)); //NOI18N
0652: } catch (IOException ex) {
0653: throw new ResponseException(ex);
0654: }
0655: }
0656:
0657: /**
0658: * Execute a command. Do not forget to initialize the CVS Root on globalOptions first!
0659: * Example:
0660: * <code>
0661: * GlobalOptions options = new GlobalOptions();
0662: * options.setCVSRoot(":pserver:"+userName+"@"+hostName+":"+cvsRoot);
0663: * </code>
0664: * @param command the command to execute
0665: * @param options the global options to use for executing the command
0666: * @throws CommandException if an error occurs when executing the command
0667: * @throws CommandAbortedException if the command is aborted
0668: */
0669: public boolean executeCommand(Command command,
0670: GlobalOptions globalOptions) throws CommandException,
0671: CommandAbortedException, AuthenticationException {
0672: BugLog.getInstance().assertNotNull(command);
0673: BugLog.getInstance().assertNotNull(globalOptions);
0674:
0675: this .globalOptions = globalOptions;
0676:
0677: getUncompressedFileHandler().setGlobalOptions(globalOptions);
0678: getGzipFileHandler().setGlobalOptions(globalOptions);
0679:
0680: try {
0681: eventManager.addCVSListener(command);
0682: command.execute(this , eventManager);
0683: } finally {
0684: eventManager.removeCVSListener(command);
0685: }
0686: return !command.hasFailed();
0687: }
0688:
0689: /**
0690: * Counts {@link #processRequests(java.util.List)}. send and received bytes.
0691: *
0692: * @thread it assumes that client is not run in paralel.
0693: */
0694: public long getCounter() {
0695: long ret = 0;
0696: if (loggedDataInputStream != null) {
0697: ret += loggedDataInputStream.getCounter();
0698: }
0699: if (loggedDataOutputStream != null) {
0700: ret += loggedDataOutputStream.getCounter();
0701: }
0702: return ret;
0703: }
0704:
0705: /**
0706: * Convert a <i>pathname</i> in the CVS sense (see 5.10 in the protocol
0707: * document) into a local absolute pathname for the file.
0708: * @param localDirectory the name of the local directory, relative to the
0709: * directory in which the command was given
0710: * @param repository the full repository name for the file
0711: */
0712: public String convertPathname(String localDirectory,
0713: String repository) {
0714: int lastIndexOfSlash = repository.lastIndexOf('/');
0715: String filename = repository.substring(lastIndexOfSlash + 1);
0716:
0717: if (localDirectory.startsWith("./")) { // NOI18N
0718: // remove the dot
0719: localDirectory = localDirectory.substring(1);
0720: }
0721: if (localDirectory.startsWith("/")) { // NOI18N
0722: // remove the leading slash
0723: localDirectory = localDirectory.substring(1);
0724: }
0725: // note that the localDirectory ends in a slash
0726: return getLocalPath() + '/' + localDirectory + filename;
0727: }
0728:
0729: /**
0730: * Get the repository path from the connection.
0731: *
0732: * @return the repository path, e.g. /home/bob/cvs. Delegated to the
0733: * Connection in this case
0734: * @see Connection#getRepository()
0735: */
0736: public String getRepository() {
0737: return connection.getRepository();
0738: }
0739:
0740: /**
0741: * Create or update the administration files for a particular file.
0742: * This will create the CVS directory if necessary, and the
0743: * Root and Repository files if necessary. It will also update
0744: * the Entries file with the new entry
0745: * @param localDirectory the local directory, relative to the directory
0746: * in which the command was given, where the file in question lives
0747: * @param repositoryPath the path of the file in the repository, in
0748: * absolute form.
0749: * @param entry the entry object for that file
0750: */
0751: public void updateAdminData(String localDirectory,
0752: String repositoryPath, Entry entry) throws IOException {
0753: final String absolutePath = localPath + '/' + localDirectory;
0754: if (repositoryPath.startsWith(getRepository())) {
0755: repositoryPath = repositoryPath.substring(getRepository()
0756: .length() + 1);
0757: } else {
0758: if (warned == false) {
0759: String warning = "#65188 warning C/S protocol error (section 5.10). It's regurarly observed with cvs 1.12.xx servers.\n"; // NOI18N
0760: warning += " unexpected pathname=" + repositoryPath
0761: + " missing root prefix=" + getRepository()
0762: + "\n"; // NOI18N
0763: warning += " relaxing, but who knows all consequences...."; // NOI18N
0764: System.err.println(warning);
0765: warned = true;
0766: }
0767: }
0768:
0769: adminHandler.updateAdminData(absolutePath, repositoryPath,
0770: entry, globalOptions);
0771: }
0772:
0773: /**
0774: * Set the modified date of the next file to be written. The next call
0775: * to write<Type>File will use this date.
0776: * @param modifiedDate the date the file should be marked as modified
0777: */
0778: public void setNextFileDate(Date modifiedDate) {
0779: this .modifiedDate = modifiedDate;
0780: }
0781:
0782: /**
0783: * Get the modified date for the next file.
0784: * @return the date and then null the instance variable.
0785: */
0786: public Date getNextFileDate() {
0787: //
0788: // We null the instance variable so that future calls will not
0789: // retrieve a date specified for a previous file
0790: //
0791: Date copy = modifiedDate;
0792: modifiedDate = null;
0793: return copy;
0794: }
0795:
0796: /**
0797: * Get the Entry for the specified file, if one exists.
0798: * @param f the file
0799: * @throws IOException if the Entries file cannot be read
0800: */
0801: public Entry getEntry(File f) throws IOException {
0802: return adminHandler.getEntry(f);
0803: }
0804:
0805: /**
0806: * Get the entries for a specified directory.
0807: * @param directory the directory for which to get the entries
0808: * @return an iterator of Entry objects
0809: */
0810: public Iterator getEntries(File directory) throws IOException {
0811: return adminHandler.getEntries(directory);
0812: }
0813:
0814: public boolean exists(File file) {
0815: return adminHandler.exists(file);
0816: }
0817:
0818: /**
0819: * Get the repository path for a given directory, for example in
0820: * the directory /home/project/foo/bar, the repository directory
0821: * might be /usr/cvs/foo/bar. The repository directory is commonly
0822: * stored in the file <pre>Repository</pre> in the CVS directory on
0823: * the client (this is the case in the standard CVS command-line tool).
0824: *
0825: * If no <pre>CVS/Repository</pre> file was found, the specified directory,
0826: * the localpath are used to "guess" the repository path.
0827: *
0828: * @param directory the directory
0829: */
0830: public String getRepositoryForDirectory(String directory)
0831: throws IOException {
0832: try {
0833: String repository = adminHandler.getRepositoryForDirectory(
0834: directory, getRepository());
0835: return repository;
0836: } catch (IOException ex) {
0837: // an IOException is thrown, if the adminHandler can't detect the repository
0838: // by reading the CVS/Repository file, e.g. when checking out into a new directory
0839: try {
0840: directory = new File(directory).getCanonicalPath();
0841: } catch (IOException ioex) {
0842: }
0843: directory = directory.replace('\\', '/');
0844: while (directory.endsWith("/")) { // NOI18N
0845: directory = directory.substring(0,
0846: directory.length() - 1);
0847: }
0848:
0849: // must also canonicalize 'localPath' to be in sync with 'directory'
0850: String localPathCanonical = getLocalPath();
0851: try {
0852: localPathCanonical = new File(getLocalPath())
0853: .getCanonicalPath();
0854: } catch (IOException ioex) {
0855: }
0856: localPathCanonical = localPathCanonical.replace('\\', '/');
0857: while (localPathCanonical.endsWith("/")) { // NOI18N
0858: localPathCanonical = localPathCanonical.substring(0,
0859: localPathCanonical.length() - 1);
0860: }
0861: int localPathLength = localPathCanonical.length();
0862:
0863: String repository;
0864: if (directory.length() >= localPathLength) {
0865: repository = getRepository()
0866: + directory.substring(localPathLength);
0867: } else { // Asking for some folder upon the local working path
0868: repository = getRepository();
0869: }
0870: return repository;
0871: }
0872: }
0873:
0874: public String getRepositoryForDirectory(File directory)
0875: throws IOException {
0876: return adminHandler.getRepositoryForDirectory(directory
0877: .getAbsolutePath(), getRepository());
0878: }
0879:
0880: /**
0881: * Set the Entry for the specified file.
0882: * @param file the file
0883: * @param entry the new entry
0884: * @throws IOException if an error occurs writing the details
0885: */
0886: public void setEntry(File file, Entry entry) throws IOException {
0887: adminHandler.setEntry(file, entry);
0888: }
0889:
0890: /**
0891: * Remove the Entry for the specified file.
0892: * @param file the file whose entry is to be removed
0893: * @throws IOException if an error occurs writing the Entries file
0894: */
0895: public void removeEntry(File file) throws IOException {
0896: adminHandler.removeEntry(file);
0897: }
0898:
0899: /**
0900: * Remove the specified file from the local disk.
0901: * If the file does not exist, the operation does nothing.
0902:
0903: * @param pathname the full path to the file to remove
0904: * @throws IOException if an IO error occurs while removing the file
0905: */
0906: public void removeLocalFile(String pathname) throws IOException {
0907: transmitFileHandler.removeLocalFile(pathname);
0908: }
0909:
0910: /**
0911: * Removes the specified file determined by pathName and repositoryName.
0912: * In this implementation the filename from repositoryPath is added
0913: * to the localpath (which doesn't have the filename in it) and that file is deleted.
0914: */
0915: public void removeLocalFile(String pathName, String repositoryName)
0916: throws IOException {
0917: int ind = repositoryName.lastIndexOf('/');
0918: if (ind <= 0) {
0919: return;
0920: }
0921:
0922: String fileName = repositoryName.substring(ind + 1);
0923: String localFile = pathName + fileName;
0924: File fileToDelete = new File(getLocalPath(), localFile);
0925: removeLocalFile(fileToDelete.getAbsolutePath());
0926: removeEntry(fileToDelete);
0927: }
0928:
0929: public void copyLocalFile(String pathname, String newName)
0930: throws IOException {
0931: transmitFileHandler.copyLocalFile(pathname, newName);
0932: }
0933:
0934: /**
0935: * Get the CVS event manager. This is generally called by response handlers
0936: * that want to fire events.
0937: * @return the eventManager
0938: */
0939: public EventManager getEventManager() {
0940: return eventManager;
0941: }
0942:
0943: /**
0944: * Get the global options that are set to this client.
0945: * Individual commands can get the global options via this method.
0946: */
0947: public GlobalOptions getGlobalOptions() {
0948: return globalOptions;
0949: }
0950:
0951: /**
0952: * Call this method to abort the current command. The command will be
0953: * aborted at the next suitable time
0954: */
0955: public synchronized void abort() {
0956: abort = true;
0957: }
0958:
0959: /**
0960: * Get all the files contained within a given
0961: * directory that are <b>known to CVS</b>.
0962: * @param directory the directory to look in
0963: * @return a set of all files.
0964: */
0965: public Set getAllFiles(File directory) throws IOException {
0966: return adminHandler.getAllFiles(directory);
0967: }
0968:
0969: public void setIgnoreFileFilter(IgnoreFileFilter ignoreFileFilter) {
0970: this .ignoreFileFilter = ignoreFileFilter;
0971: }
0972:
0973: public IgnoreFileFilter getIgnoreFileFilter() {
0974: return ignoreFileFilter;
0975: }
0976:
0977: public boolean shouldBeIgnored(File directory, String noneCvsFile) {
0978: if (ignoreFileFilter != null) {
0979: return ignoreFileFilter.shouldBeIgnored(directory,
0980: noneCvsFile);
0981: }
0982: return false;
0983: }
0984:
0985: /**
0986: * Checks for presence of CVS/Tag file and returns it's value.
0987: * @return the value of CVS/Tag file for the specified directory
0988: * null if file doesn't exist
0989: */
0990: public String getStickyTagForDirectory(File directory) {
0991: return adminHandler.getStickyTagForDirectory(directory);
0992: }
0993:
0994: /**
0995: * This method is called when a response for the ValidRequests request
0996: * is received.
0997: * @param requests A List of requests that is valid for this CVS server
0998: * separated by spaces.
0999: */
1000: public void setValidRequests(String requests) {
1001: // We need to tokenize the requests and add it to our map
1002:
1003: StringTokenizer tokenizer = new StringTokenizer(requests);
1004: String token;
1005: while (tokenizer.hasMoreTokens()) {
1006: token = tokenizer.nextToken();
1007: // we just add an object with the corresponding request
1008: // as the key.
1009: validRequests.put(token, this );
1010: }
1011:
1012: }
1013:
1014: private int fillInitialRequests(List requests) {
1015: int pos = 0;
1016: requests.add(pos++, new RootRequest(getRepository()));
1017: requests.add(pos++, new UseUnchangedRequest());
1018: requests.add(pos++, new ValidRequestsRequest());
1019: requests.add(pos++, new ValidResponsesRequest());
1020: return pos;
1021: }
1022:
1023: /** This method is called by WrapperSendResponse for each wrapper setting sent
1024: * back by the CVS server
1025: * @param pattern A StringPattern indicating the pattern for which the
1026: * wrapper applies
1027: * @param option A KeywordSubstituionOption corresponding to the setting
1028: */
1029: public void addWrapper(StringPattern pattern,
1030: KeywordSubstitutionOptions option) {
1031: if (wrappersMap == null) {
1032: throw new IllegalArgumentException(
1033: "This method should be called "
1034: + "by WrapperSendResponse only.");
1035: }
1036: wrappersMap.put(pattern, option);
1037: }
1038:
1039: /** Returns the wrappers map associated with the CVS server
1040: * The map is valid only after the connection is established
1041: */
1042: public Map getWrappersMap() throws CommandException {
1043: if (wrappersMap == null) {
1044: wrappersMap = new HashMap();
1045: ArrayList requests = new ArrayList();
1046: requests.add(new WrapperSendRequest());
1047: boolean isFirst = isFirstCommand();
1048: try {
1049: processRequests(requests);
1050: } catch (Exception ex) {
1051: throw new CommandException(ex,
1052: "An error during obtaining server wrappers.");
1053: } finally {
1054: // Do not alter isFirstCommand property
1055: setIsFirstCommand(isFirst);
1056: }
1057: wrappersMap = Collections.unmodifiableMap(wrappersMap);
1058: }
1059: return wrappersMap;
1060: }
1061:
1062: /**
1063: * Factory for creating clients.
1064: */
1065: public static interface Factory {
1066:
1067: /**
1068: * Creates new client instance. Never null.
1069: * It uses fresh connection.
1070: */
1071: Client createClient();
1072: }
1073: }
|