/* * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved. * * http://izpack.org/ * http://izpack.codehaus.org/ * * Copyright 2002 Elmar Grom * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.izforge.izpack.util.os; import com.izforge.izpack.util.Librarian; import com.izforge.izpack.util.NativeLibraryClient; import java.io.File; import java.io.UnsupportedEncodingException; /*---------------------------------------------------------------------------*/ /** * This class represents a MS-Windows shell link, aka shortcut. It supports creation, modification * and deletion as well as reporting on details of shell links. This class uses a number of native * methods to access the MS-Windows registry and load save and manipulate link data. The native code * is contained in the file <code>ShellLink.cpp</code>. <br> * <br> * For more detailed information on Windows shortcuts read the win32 documentation from Microsoft on * the IShellLink interface. There are also useful articles on this topic on the MIcrosoft website. * <br> * <br> * <A * HREF=http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmgmt/html/msdn_shellnk1.asp>Using * Shell Links in Windows 95</A><br> * <A * HREF=http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/ifaces/ishelllink/ishelllink.asp>The * IShellLink interface</a><br> * <A * HREF=http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/Shell/IFaces/IShellLink/IShellLink.asp>IShellLink</A> * * @author Elmar Grom * @version 0.0.1 / 1/21/02 */ /*---------------------------------------------------------------------------*/ public class ShellLink implements NativeLibraryClient { // ------------------------------------------------------------------------ // Constant Definitions // ------------------------------------------------------------------------ /** * Note: each of the subclasses will convert these values as appropriate before calling the OS's * routines. For example Win 98 & up will use SW_SNOWMINNOACTIVE (7) when passed HIDE (0) or * MINIMIZED (2) <br> * <br> * and this conversion is done in Win_Shortcut.java */ /** * Hide the window when starting. This is particularly useful when launching from a *.bat file, * because no DOS window and no button for the DOS window on the task bar will show! <br> * <br> * <b>Note:</b> this option is not available through the Windows 98+ UI! */ public static final int HIDE = 0; /** * Show the window 'normal' when starting. Restores the window properties at the last shut-down. */ public static final int NORMAL = 1; /** * Show the window minimized when starting. The window will not show but a corresponding button * in the task bar will. <p> * <p/> * Newer IShellLink only allows Normal, MinNoActive, Maximized. */ public static final int MINIMIZED = 2; /** * Show the window maximized when starting. */ public static final int MAXIMIZED = 3; /** * Show the window minimized when starting. Note: for win98 and newer, use MINNOACTIVE instead * of MINIMIZED. */ public static final int MINNOACTIVE = 7; private static final int MIN_SHOW = 0; private static final int MAX_SHOW = 7; // ------------------------------------------------------ // Shortcut types // specific to ShellLink (Shortcut has different numbers). // ------------------------------------------------------ /** * This type of shortcut shows on the desktop */ public static final int DESKTOP = 1; /** * This type of shortcut shows in the program menu */ public static final int PROGRAM_MENU = 2; /** * This type of shortcut shows in the start menu */ public static final int START_MENU = 3; /** * This type of shortcut is executed at OS launch time */ public static final int STARTUP = 4; private static final int MIN_TYPE = 1; private static final int MAX_TYPE = 4; // ------------------------------------------------------ // Return values from nafive methods // ------------------------------------------------------ /** * Returned from native calls if the call was successful */ private static final int SL_OK = 1; /** * Unspecific return if a native call was not successful */ private static final int SL_ERROR = -1; /** * Return value from native initialization functions if already initialized */ private static final int SL_INITIALIZED = -2; /** * Return value from native uninitialization functions if never initialized */ private static final int SL_NOT_INITIALIZED = -3; /** * Return value from native uninitialization functions if there are no more interface handles * available */ private static final int SL_OUT_OF_HANDLES = -4; /** * Return value from native uninitialization functions if nohandle for the IPersist interface * could be obtained */ private static final int SL_NO_IPERSIST = -5; /** * Return value from native uninitialization functions if the save operation fort the link * failed */ private static final int SL_NO_SAVE = -6; /** * Return value if the function called had to deal with unexpected data types. This might be * returned by registry functions if they receive an unexpected data type from the registry. */ private static final int SL_WRONG_DATA_TYPE = -7; // ------------------------------------------------------ // Miscellaneous constants // ------------------------------------------------------ private static final int UNINITIALIZED = -1; /** * the extension that must be used for link files */ private static final String LINK_EXTENSION = ".lnk"; /** * CURRENT_USER = 0; the constant to use for selecting the current user. */ public static final int CURRENT_USER = 0; /** * ALL_USERS = 1; the constant to use for selecting the all users. */ public static final int ALL_USERS = 1; // ------------------------------------------------------------------------ // Variable Declarations // ------------------------------------------------------------------------ /** * This handle links us to a specific native instance. Do not use or modify, the variable is for * exclusive use by the native side. */ private int nativeHandle = UNINITIALIZED; /** * Path to the location where links for the current user are stored. The exact content depends * on the circumstances. It can be set during object construction or from native code. It will * point to the location where links of the most recently requested type are stored. */ private String currentUserLinkPath; /** * Path to the location where links for all users are stored. The exact content depends on the * circumstances. It can be set during object construction or from native code. It will point to * the location where links of the most recently requested type are stored. */ private String allUsersLinkPath; private String groupName = ""; private String linkName = ""; /** * this is the fully qualified name of the link on disk. Note that this variable contains only * valid data if the link was created from a disk file or after a successful save operation. At * other times the content is upredicatable. */ private String linkFileName = ""; /** * Contains the directory where the link file is stored after any save operation that needs to * create that directory. Otherwise it contains <code>null</code>. */ private String linkDirectory = ""; private String arguments = ""; private String description = ""; private String iconPath = ""; private String targetPath = ""; private String workingDirectory = ""; /** * there seems to be an error in JNI that causes an access violation if a String that is * accessed from native code borders on another type of variable. This caused problems in * <code>set()</code> For this reason, the dummy string is placed here. Observed with version: * <p/> * <pre> * <p/> * <p/> * <p/> * java version "1.3.0" * Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C) * Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode) * <p/> * <p/> * </pre> */ private String dummyString = ""; private int hotkey = 0; private int iconIndex = 0; private int showCommand = NORMAL; private int linkType = DESKTOP; private int userType = CURRENT_USER; private boolean initializeSucceeded = false; // ------------------------------------------------------------------------ // Native Methods // ------------------------------------------------------------------------ // For documentation on these methods see ShellLink.cpp // ------------------------------------------------------------------------ private native int initializeCOM(); private native int releaseCOM(); private native int getInterface(); private native int releaseInterface(); private native int GetArguments(); private native int GetDescription(); private native int GetHotkey(); private native int GetIconLocation(); private native int GetPath(); private native int GetShowCommand(); private native int GetWorkingDirectory(); private native int Resolve(); private native int SetArguments(); private native int SetDescription(); private native int SetHotkey(); private native int SetIconLocation(); private native int SetPath(); private native int SetShowCommand(); private native int SetWorkingDirectory(); private native int saveLink(String name); private native int loadLink(String name); private native int GetFullLinkPath(int usertype, int linktype); /** * This method is used to free the library at the end of progam execution. After this call, any * instance of this calss will not be usable any more! */ private native void FreeLibrary(String name); /** * Creates an instance of <code>ShellLink</code> of a specific type. Initializes * currentUserLinkPath and allUsersLinkPath. * <p/> * <p/> * A LinkPath is empty if the combination of linkType and userType, are not valid. * <p/> * <p/> * Note: If a linkPath is empty, the userType is reset to the other userType. * <p/> * <p/> * If both linkPaths are empty, an IllegalArgumentException is thrown. * * @param type The type of link desired. The following values can be set:<br> * <ul> * <li><code>ShellLink.DESKTOP</code> * <li><code>ShellLink.PROGRAM_MENU</code> * <li><code>ShellLink.START_MENU</code> * <li><code>ShellLink.STARTUP</code> * </ul> * @param name The name that the link should display on a menu or on the desktop. Do not include * a file extension. * @throws IllegalArgumentException if any of the call parameters are incorrect, or if no * linkPaths are returned. * @throws Exception if problems are encountered in initializing the native interface */ public ShellLink(int type, String name) throws Exception, IllegalArgumentException { if ((type < MIN_TYPE) || (type > MAX_TYPE)) { throw (new IllegalArgumentException( "the type parameter used an illegal value")); } if (name == null) { throw (new IllegalArgumentException("the name parameter was null")); } linkName = name; linkType = type; initialize(); // com // set curretnUsersLinkPath, allUsersLinkPath, sets userType to valid. setAllLinkPaths(); } /*--------------------------------------------------------------------------*/ /** * Creates an instance of <code>ShellLink</code> from an existing shell link on disk. * * @param name the fully qualified file name of the link. * @param userType the type of user for the link path. * @throws IllegalArgumentException if the name was null * @throws Exception if problems are encountered in reading the file * @see #CURRENT_USER * @see #ALL_USERS */ public ShellLink(String name, int userType) throws Exception, IllegalArgumentException { if (name == null) { throw (new IllegalArgumentException("the name parameter was null")); } this.userType = userType; initialize(); // com // store the individual parts of the path for later use int pathEnd = name.lastIndexOf(File.separator); int nameStart = pathEnd + 1; int nameEnd = name.lastIndexOf('.'); if (nameEnd < 0) { throw (new Exception("illegal file name")); } linkName = name.substring(nameStart, nameEnd); if (userType == CURRENT_USER) { currentUserLinkPath = name.substring(0, pathEnd); } else { allUsersLinkPath = name.substring(0, pathEnd); } linkFileName = fullLinkName(userType); if (loadLink(linkFileName) != SL_OK) { throw (new Exception( "reading of the file did not succeed")); } // get all settings from the native side get(); } /*--------------------------------------------------------------------------*/ /** * Creates an instance of <code>ShellLink</code> from an existing shell link on disk. * * @param type The type of link, one of the following values: <br> * <ul> * <li><code>ShellLink.DESKTOP</code> * <li><code>ShellLink.PROGRAM_MENU</code> * <li><code>ShellLink.START_MENU</code> * <li><code>ShellLink.STARTUP</code> * </ul> * @param userType the type of user for the link path. * @param group The program group (directory) of this link. If the link is not part of a program * group, pass an empty string or null for this parameter. (...\\Desktop\\group). * @param name The file name of this link. Do not include a file extension. * @throws IllegalArgumentException if any of the call parameters are incorrect * @throws Exception if problems are encountered in initializing the native interface * @see #CURRENT_USER * @see #ALL_USERS */ public ShellLink(int type, int userType, String group, String name) throws Exception, IllegalArgumentException { if ((type < MIN_TYPE) || (type > MAX_TYPE)) { throw (new IllegalArgumentException( "the type parameter used an illegal value")); } if (name == null) { throw (new IllegalArgumentException("the name parameter was null")); } this.userType = userType; initialize(); // com // set the variables for currentUserLinkPath and allUsersLinkPath setAllLinkPaths(); if (group != null) { groupName = group; } linkName = name; // load the link linkFileName = fullLinkName(userType); if (loadLink(linkFileName) != SL_OK) { throw (new Exception( "reading of the file did not succeed")); } // get a settings from the native side get(); } /*--------------------------------------------------------------------------*/ /** * Initializes COM and gets an instance of the IShellLink interface. * * @throws Exception if problems are encountered */ private void initialize() throws Exception { try { Librarian.getInstance().loadLibrary("ShellLink", this); } catch (UnsatisfiedLinkError exception) { throw (new Exception("could not locate native library")); } try { if (initializeCOM() != SL_OK) { throw (new Exception("could not initialize COM")); } else { initializeSucceeded = true; } } catch (Throwable exception) { throw (new Exception("unidentified problem initializing COM\n" + exception.toString())); } int successCode = getInterface(); if (successCode != SL_OK) { releaseCOM(); initializeSucceeded = false; if (successCode == SL_OUT_OF_HANDLES) { throw (new Exception( "could not get an instance of IShellLink, no more handles available")); } else { throw (new Exception( "could not get an instance of IShellLink, failed to co-create instance")); } } } /*--------------------------------------------------------------------------*/ /** * Destructor, releases COM and frees native resources. */ protected void finalize() throws Throwable { releaseInterface(); if (initializeSucceeded) { releaseCOM(); initializeSucceeded = false; } super.finalize(); } /*--------------------------------------------------------------------------*/ /** * This method is used to free the library at the end of progam execution. After this call, any * instance of this calss will not be usable any more! <b><i><u>Note that this method does NOT * return!</u></i></b> <br> * <br> * <b>DO NOT CALL THIS METHOD DIRECTLY!</b><br> * It is used by the librarian to free the native library before physically deleting it from its * temporary loaction. A call to this method will freeze the application irrecoverably! * * @param name the name of the library to free. Use only the name and extension but not the * path. * @see com.izforge.izpack.util.NativeLibraryClient#freeLibrary */ public void freeLibrary(String name) { int result = releaseInterface(); if (initializeSucceeded) { result = releaseCOM(); initializeSucceeded = false; } FreeLibrary(name); } /*--------------------------------------------------------------------------*/ /** * Constructs and returns the full path for the link file. * * @param userType the type of user for the link path. * @return the path to use for storing the link * @see #CURRENT_USER * @see #ALL_USERS */ private String fullLinkPath(int userType) { StringBuffer path = new StringBuffer(); // ---------------------------------------------------- // build the complete name // ---------------------------------------------------- if (userType == CURRENT_USER) { path.append(currentUserLinkPath); } else { path.append(allUsersLinkPath); } if ((groupName != null) && (groupName.length() > 0)) { path.append(File.separator); path.append(groupName); } return (path.toString()); } /*--------------------------------------------------------------------------*/ /** * Constructs and returns the fully qualified name for the link file. * * @param userType the type of user for the link path. * @return the fully qualified file name to use for storing the link * @see #CURRENT_USER * @see #ALL_USERS */ private String fullLinkName(int userType) { StringBuffer name = new StringBuffer(); name.append(fullLinkPath(userType)); name.append(File.separator); name.append(linkName); name.append(LINK_EXTENSION); return (name.toString()); } /*--------------------------------------------------------------------------*/ /** * Sets all members on the native side. * * @throws Exception if any problem is encountered during this operation. */ private void set() throws Exception { if (SetArguments() != SL_OK) { throw (new Exception("could not set arguments")); } if (SetDescription() != SL_OK) { throw (new Exception("could not set description")); } if (SetHotkey() != SL_OK) { throw (new Exception("could not set hotkey")); } if (SetIconLocation() != SL_OK) { throw (new Exception("could not set icon location")); } if (SetPath() != SL_OK) { throw (new Exception("could not set target path")); } if (SetShowCommand() != SL_OK) { throw (new Exception("could not set show command")); } if (SetWorkingDirectory() != SL_OK) { throw (new Exception( "could not set working directory")); } } /*--------------------------------------------------------------------------*/ /** * Gets all members from the native side. * * @throws Exception if any problem is encountered during this operation. */ private void get() throws Exception { if (GetArguments() != SL_OK) { throw (new Exception("could not get arguments")); } if (GetDescription() != SL_OK) { throw (new Exception("could not get description")); } if (GetHotkey() != SL_OK) { throw (new Exception("could not get hotkey")); } if (GetIconLocation() != SL_OK) { throw (new Exception("could not get icon location")); } if (GetPath() != SL_OK) { throw (new Exception("could not get target ath")); } if (GetShowCommand() != SL_OK) { throw (new Exception("could not get show command")); } if (GetWorkingDirectory() != SL_OK) { throw (new Exception( "could not get working directory")); } } /*--------------------------------------------------------------------------*/ /** * Sets the name of the program group this ShellLinbk should be placed in. * * @param groupName the name of the program group */ public void setProgramGroup(String groupName) { this.groupName = groupName; } /*--------------------------------------------------------------------------*/ /** * Sets the command line arguments that will be passed to the target when the link is activated. * * @param arguments the command line arguments * @see #getArguments */ public void setArguments(String arguments) { this.arguments = arguments; } /*--------------------------------------------------------------------------*/ /** * Sets the description string that is used to identify the link in a menu or on the desktop. * * @param description the descriptiojn string * @see #getDescription */ public void setDescription(String description) { this.description = description; } /*--------------------------------------------------------------------------*/ /** * Sets the hotkey that can be used to activate the link. * * @param hotkey a valid Windows virtual key code. Modifiers (e.g. for alt or shift key) are * added in the upper byte. Note that only the lower 16 bits for tis parameter are used. * @see #getHotkey */ public void setHotkey(int hotkey) { this.hotkey = hotkey; } /*--------------------------------------------------------------------------*/ /** * Sets the location of the icon that is shown for the shortcut on the desktop. * * @param path a fully qualified file name of a file that contains the icon. * @param index the index of the specific icon to use in the file. If there is only one icon in * the file, use an index of 0. * @see #getIconLocation */ public void setIconLocation(String path, int index) { this.iconPath = path; this.iconIndex = index; } /*--------------------------------------------------------------------------*/ /** * Sets the absolute path to the shortcut target. * * @param path the fully qualified file name of the target * @see #getTargetPath */ public void setTargetPath(String path) { this.targetPath = path; } /*--------------------------------------------------------------------------*/ /** * Sets the show command that is passed to the target application when the link is activated. * The show command determines if the the window will be restored to the previous size, * minimized, maximized or visible at all. <br> * <br> * <b>Note:</b><br> * Using <code>HIDE</code> will cause the target window not to show at all. There is not even * a button on the taskbar. This is a very useful setting when batch files are used to launch a * Java application as it will then appear to run just like any native Windows application.<br> * <b>Note1:</b><br> * <code>HIDE</code> doesn't work in Win98 and newer systems.<br> * use MINIMIZED (MINNOACTIVE), instead.<br> * * @param show the show command. Valid settings are: <br> * <ul> * <li><code>ShellLink.HIDE</code> (deprecated) * <li><code>ShellLink.NORMAL</code> * <li><code>ShellLink.MINNOACTIVE</code> * <li><code>ShellLink.MAXIMIZED</code> * </ul> * @see #getShowCommand */ public void setShowCommand(int show) { if ((show < MIN_SHOW) || (show > MAX_SHOW)) { throw (new IllegalArgumentException( "illegal value for show command " + show)); } this.showCommand = show; } /*--------------------------------------------------------------------------*/ /** * Sets the working directory for the link target. * * @param dir the working directory * @see #getWorkingDirectory */ public void setWorkingDirectory(String dir) { this.workingDirectory = dir; } /*--------------------------------------------------------------------------*/ /** * Sets the name shown in a menu or on the desktop for the link. * * @param name The name that the link should display on a menu or on the desktop. Do not include * a file extension. */ public void setLinkName(String name) { linkName = name; } /*--------------------------------------------------------------------------*/ /** * Sets the type of link * * @param type The type of link desired. The following values can be set:<br> * <ul> * <li>{@link #DESKTOP} * <li>{@link #PROGRAM_MENU} * <li>{@link #START_MENU} * <li>{@link #STARTUP} * </ul> * @throws IllegalArgumentException if an an invalid type is passed * @throws UnsupportedEncodingException */ public void setLinkType(int type) throws IllegalArgumentException, UnsupportedEncodingException { if ((type < MIN_TYPE) || (type > MAX_TYPE)) { throw (new IllegalArgumentException( "illegal value for type")); } linkType = type; // set curretnUsersLinkPath, allUsersLinkPath, sets userType to valid. setAllLinkPaths(); } /** * Returns the user type for the link. <br> * <ul> * <li>{@link #DESKTOP} * <li>{@link #PROGRAM_MENU} * <li>{@link #START_MENU} * <li>{@link #STARTUP} * </ul> * <br> * * @see #setLinkType */ public int getLinkType() { return linkType; } /*--------------------------------------------------------------------------*/ /** * Sets the (ShellLink) user type for link * * @param type the type of user for the link. * @throws IllegalArgumentException if an an invalid type is passed * @see #CURRENT_USER * @see #ALL_USERS */ public void setUserType(int type) throws IllegalArgumentException { if ((type == CURRENT_USER) || (type == ALL_USERS)) { userType = type; } else { throw (new IllegalArgumentException(type + " is not a recognized user type")); } } /*--------------------------------------------------------------------------*/ /** * Returns the (ShellLink) user type for the link. Either {@link #CURRENT_USER} or * {@link #ALL_USERS} * * @see #setUserType */ public int getUserType() { return userType; } /*--------------------------------------------------------------------------*/ /** * Returns the path where the links of the selected type are stroed. This method is useful for * discovering which program groups already exist. * * @param userType the type of user for the link path. One of {@link #CURRENT_USER} or * {@link #ALL_USERS} * @return the path to the type of link set for this instance. */ public String getLinkPath(int userType) { String result = null; if (userType == CURRENT_USER) { result = currentUserLinkPath; } else { result = allUsersLinkPath; } return result; } /*--------------------------------------------------------------------------*/ /** * Returns the command line that the link passes to the target. * * @return the command line * @see #setArguments */ public String getArguments() { return (arguments); } /*--------------------------------------------------------------------------*/ /** * Returns the description for the link. * * @return the description * @see #setDescription */ public String getDescription() { return (description); } /*--------------------------------------------------------------------------*/ /** * Retruns the hotkey that can be used to activate the link. * * @return the virtual keycode for the hotkey * @see #setHotkey */ public int getHotkey() { return (hotkey); } /*--------------------------------------------------------------------------*/ /** * Returns the path and file name of the file that contains the icon that is associated with the * link. * * @return the path to the icon * @see #setIconLocation */ public String getIconLocation() { return (iconPath); } /*--------------------------------------------------------------------------*/ /** * Returns the index of the icon with the icon or resource file * * @return the index * @see #setIconLocation */ public int getIconIndex() { return (iconIndex); } /*--------------------------------------------------------------------------*/ /** * Retruns the absolute path of the link target * * @return the path * @see #setTargetPath */ public String getTargetPath() { return (targetPath); } /*--------------------------------------------------------------------------*/ /** * Returns the initial condition of the target window (HIDE, NORMAL, MINIMIZED, MAXIMIZED). * * @return the target show command * @see #setShowCommand */ public int getShowCommand() { return (showCommand); } /*--------------------------------------------------------------------------*/ /** * Retruns the working deirectory for the link target. * * @return the working directory * @see #setWorkingDirectory */ public String getWorkingDirectory() { return (workingDirectory); } /*--------------------------------------------------------------------------*/ /** * Returns the fully qualified file name under which the link is saved on disk. <b>Note:</b> * this method returns valid results only if the instance was created from a file on disk or * after a successful save operation. * * @return the fully qualified file name for the shell link */ public String getFileName() { return (linkFileName); } /*--------------------------------------------------------------------------*/ /** * Returns the path of the directory where the link file is stored, if it was necessary during * the previous save operation to create the directory. This method returns <code>null</code> * if no save operation was carried out or there was no need to create a directory during the * previous save operation. * * @return the path of the directory where the link file is stored or <code>null</code> if no * save operation was carried out or there was no need to create a directory during the previous * save operation. */ public String getDirectoryCreated() { return (linkDirectory); } /*--------------------------------------------------------------------------*/ /** * Returns the name shown in a menu or on the desktop for the link. * * @return the name */ public String getLinkName() { return (linkName); } /*--------------------------------------------------------------------------*/ /** * Returns the path for currentusersLink * * @return currentUsersLinkPath */ public String getcurrentUserLinkPath() { return currentUserLinkPath; } /*--------------------------------------------------------------------------*/ /** * Returns the path for allusersLink * * @return allusersLinkPath */ public String getallUsersLinkPath() { return allUsersLinkPath; } /*--------------------------------------------------------------------------*/ /** * Saves this link. * * @throws Exception if problems are encountered */ public void save() throws Exception { // set all values on the native side set(); // make sure the target actually resolves int result = Resolve(); if (result != SL_OK) { throw (new Exception("cannot resolve target")); } // make sure the directory exists File directory = new File(fullLinkPath(userType)); if (!directory.exists()) { directory.mkdirs(); linkDirectory = directory.getPath(); } else { linkDirectory = ""; } //work-around for bug in Windows Vista where link-save to // current-user location ends up in all-users location; // if link is for current user then setup file object for // all-users location and check file last-modification time: File allUsersFileObj = null; long aufLastModVal = 0; if (userType == CURRENT_USER) { //link is for current user; setup file obj for all-users location allUsersFileObj = new File(fullLinkName(ALL_USERS)); //get last-modified time (or 0 if no file): aufLastModVal = allUsersFileObj.lastModified(); } // perform the save operation String saveTo = fullLinkName(userType); result = saveLink(saveTo); if (result == SL_NO_IPERSIST) { throw (new Exception("could not get handle for IPesist")); } else if (result == SL_NO_SAVE) { throw (new Exception("the save operation failed")); } if (allUsersFileObj != null) { //all-users file object was setup above try { if (allUsersFileObj.exists() && allUsersFileObj.lastModified() > aufLastModVal) { //link exists in all-users location and has been updated //setup file object for current-user location: final File curUserFileObj = new File(saveTo); //move link file to current-user location: moveFileTo(allUsersFileObj,curUserFileObj); //attempt to remove directory in all-users location // (will succeed if directory is empty): allUsersFileObj.getParentFile().delete(); } } catch(Exception ex) { //some kind of exception error; ignore and move on } } linkFileName = saveTo; } /*--------------------------------------------------------------------------*/ /** * Saves this link to any desired location. * * @param name the fully qualified file name for the link * @throws IllegalArgumentException if the parameter was null * @throws Exception if the save operation could not be carried out */ public void save(String name) throws Exception { if (name == null) { throw (new IllegalArgumentException("name was null")); } // set all values on the native side set(); // make sure the target actually resolves if (Resolve() != SL_OK) { throw (new Exception("cannot resolve target")); } // make sure the directory exists File directory = new File(name.substring(0, name.lastIndexOf(File.separatorChar))); if (!directory.exists()) { directory.mkdirs(); linkDirectory = directory.getPath(); } else { linkDirectory = null; } // perform the save operation if (saveLink(name) != SL_OK) { throw (new Exception("the save operation failed")); } linkFileName = name; } /** * Moves a file to a given destination. Works even if destination * file exists and needs to be overwritten. * @param srcFileObj source file to be moved. * @param destFileObj destination file object. */ protected void moveFileTo(File srcFileObj, File destFileObj) { if (destFileObj.exists()) { //destination file currently exists final File tempFileObj = //create new, unique target filename new File(destFileObj.getPath()+System.currentTimeMillis()); destFileObj.renameTo(tempFileObj); //rename dest to temp name srcFileObj.renameTo(destFileObj); //move source file to dest tempFileObj.delete(); //remove renamed original file } else //destination file does not exist srcFileObj.renameTo(destFileObj); } /*--------------------------------------------------------------------------*/ /** * sets currentUsersLinkPath and allUsersLinkPath. If the path is empty, resets userType to a * valid userType for this type of link. If no linkPaths are valid, an IllegalArgumentException * is thrown. * * @throws IllegalArgumentException * @throws UnsupportedEncodingException */ private void setAllLinkPaths() throws IllegalArgumentException { // (re)sets currentUsersLinkPath and allUsersLinkPath currentUserLinkPath = ""; allUsersLinkPath = ""; GetFullLinkPath(CURRENT_USER, linkType); GetFullLinkPath(ALL_USERS, linkType); // be sure userType is valid. Override initial choice if not. if (userType == CURRENT_USER && currentUserLinkPath.length() == 0) { userType = ALL_USERS; } else if (userType == ALL_USERS && allUsersLinkPath.length() == 0) { userType = CURRENT_USER; } if (allUsersLinkPath.length() == 0 && currentUserLinkPath.length() == 0) { throw (new IllegalArgumentException("linkType " + linkType + " is invalid.")); } } } /*---------------------------------------------------------------------------*/