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 General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.utils.system;
038:
039: import java.io.File;
040: import java.io.FileInputStream;
041: import java.io.IOException;
042: import java.io.OutputStream;
043: import java.util.LinkedList;
044: import java.util.List;
045: import javax.xml.parsers.DocumentBuilder;
046: import javax.xml.parsers.DocumentBuilderFactory;
047: import javax.xml.parsers.ParserConfigurationException;
048: import org.netbeans.installer.utils.helper.ErrorLevel;
049: import org.netbeans.installer.utils.helper.ExecutionResults;
050: import org.netbeans.installer.utils.FileUtils;
051: import org.netbeans.installer.utils.LogManager;
052: import org.netbeans.installer.utils.system.shortcut.FileShortcut;
053: import org.netbeans.installer.utils.system.shortcut.InternetShortcut;
054: import org.netbeans.installer.utils.system.shortcut.Shortcut;
055: import org.netbeans.installer.utils.SystemUtils;
056: import org.netbeans.installer.utils.XMLUtils;
057: import org.netbeans.installer.utils.exceptions.NativeException;
058: import org.netbeans.installer.utils.exceptions.XMLException;
059: import org.netbeans.installer.utils.system.shortcut.LocationType;
060: import org.w3c.dom.Document;
061: import org.w3c.dom.Element;
062: import org.w3c.dom.Node;
063: import org.xml.sax.EntityResolver;
064: import org.xml.sax.InputSource;
065: import org.xml.sax.SAXException;
066:
067: /**
068: *
069: * @author Dmitry Lipin
070: */
071: public class MacOsNativeUtils extends UnixNativeUtils {
072: /////////////////////////////////////////////////////////////////////////////////
073: // Instance
074:
075: // constructor //////////////////////////////////////////////////////////////////
076: MacOsNativeUtils() {
077: loadNativeLibrary(LIBRARY_PATH_MACOSX);
078: initializeForbiddenFiles(FORBIDDEN_DELETING_FILES_MACOSX);
079: }
080:
081: // NativeUtils implementation/override //////////////////////////////////////////
082: public File getDefaultApplicationsLocation() {
083: File applications = new File("/Applications");
084:
085: if (applications.exists() && applications.isDirectory()
086: && FileUtils.canWrite(applications)) {
087: return applications;
088: } else {
089: return SystemUtils.getUserHomeDirectory();
090: }
091: }
092:
093: private String getShortcutFilename(Shortcut shortcut) {
094: String fileName = shortcut.getFileName();
095:
096: if (fileName == null) {
097: if (shortcut instanceof FileShortcut) {
098: fileName = ((FileShortcut) shortcut).getTarget()
099: .getName();
100:
101: if (fileName != null && fileName.endsWith(APP_SUFFIX)) {
102: fileName = fileName.substring(0, fileName
103: .lastIndexOf(APP_SUFFIX));
104: }
105: } else if (shortcut instanceof InternetShortcut) {
106: fileName = ((InternetShortcut) shortcut).getURL()
107: .getFile();
108: }
109: }
110: return fileName;
111: }
112:
113: public File getShortcutLocation(Shortcut shortcut,
114: LocationType locationType) throws NativeException {
115: String fileName = getShortcutFilename(shortcut);
116:
117: switch (locationType) {
118: case CURRENT_USER_DESKTOP:
119: return new File(SystemUtils.getUserHomeDirectory(),
120: "Desktop/" + fileName);
121: case ALL_USERS_DESKTOP:
122: return new File(SystemUtils.getUserHomeDirectory(),
123: "Desktop/" + fileName);
124: case CURRENT_USER_START_MENU:
125: return getDockPropertiesFile();
126: case ALL_USERS_START_MENU:
127: return getDockPropertiesFile();
128: }
129: return null;
130: }
131:
132: protected void createURLShortcut(InternetShortcut shortcut)
133: throws NativeException {
134: try {
135: List<String> lines = new LinkedList<String>();
136: lines.add("[InternetShortcut]");
137: lines.add("URL=" + shortcut.getURL());
138: lines.add("IconFile=" + shortcut.getIconPath());
139: lines.add(SystemUtils.getLineSeparator());
140: FileUtils.writeStringList(new File(shortcut.getPath()),
141: lines);
142: } catch (IOException ex) {
143: throw new NativeException("Can`t create URL shortcut", ex);
144: }
145: }
146:
147: public File createShortcut(Shortcut shortcut,
148: LocationType locationType) throws NativeException {
149: final File shortcutFile = getShortcutLocation(shortcut,
150: locationType);
151:
152: try {
153:
154: if (locationType == LocationType.CURRENT_USER_DESKTOP
155: || locationType == LocationType.ALL_USERS_DESKTOP) {
156: // create a symlink on desktop for files/directories and .url for internet shortcuts
157: if (!shortcutFile.exists()) {
158: if (shortcut instanceof FileShortcut) {
159: createSymLink(shortcutFile,
160: ((FileShortcut) shortcut).getTarget());
161: } else if (shortcut instanceof InternetShortcut) {
162: createURLShortcut((InternetShortcut) shortcut);
163: }
164:
165: }
166: } else if (shortcut instanceof FileShortcut
167: && convertDockProperties(true) == 0) { //create link in the Dock
168: if (modifyDockLink((FileShortcut) shortcut,
169: shortcutFile, true)) {
170: LogManager.log(ErrorLevel.DEBUG,
171: " Updating Dock");
172: convertDockProperties(false);
173: SystemUtils.executeCommand(null,
174: UPDATE_DOCK_COMMAND);
175: }
176: }
177: return shortcutFile;
178: } catch (IOException e) {
179: throw new NativeException("Cannot create shortcut", e);
180: }
181: }
182:
183: public void removeShortcut(Shortcut shortcut,
184: LocationType locationType, boolean cleanupParents)
185: throws NativeException {
186: final File shortcutFile = getShortcutLocation(shortcut,
187: locationType);
188:
189: try {
190: if (locationType == LocationType.CURRENT_USER_DESKTOP
191: || locationType == LocationType.ALL_USERS_DESKTOP) {
192: // create a symlink on desktop
193: if (shortcutFile.exists()) {
194: FileUtils.deleteFile(shortcutFile, false);
195: }
196: } else if (shortcut instanceof FileShortcut
197: && convertDockProperties(true) == 0) {//create link in the Dock
198: if (modifyDockLink((FileShortcut) shortcut,
199: shortcutFile, false)) {
200: LogManager.log(ErrorLevel.DEBUG,
201: " Updating Dock");
202: if (convertDockProperties(false) == 0) {
203: SystemUtils.executeCommand(null,
204: UPDATE_DOCK_COMMAND);
205: }
206: }
207: }
208: } catch (IOException e) {
209: throw new NativeException("Cannot remove shortcut", e);
210: }
211: }
212:
213: // mac os x specific ////////////////////////////////////////////////////////////
214: public boolean isCheetah() {
215: return (getOSVersion().startsWith("10.0"));
216: }
217:
218: public boolean isPuma() {
219: return (getOSVersion().startsWith("10.1"));
220: }
221:
222: public boolean isJaguar() {
223: return (getOSVersion().startsWith("10.2"));
224: }
225:
226: public boolean isPanther() {
227: return (getOSVersion().startsWith("10.3"));
228: }
229:
230: public boolean isTiger() {
231: return (getOSVersion().startsWith("10.4"));
232: }
233:
234: public boolean isLeopard() {
235: return (getOSVersion().startsWith("10.5"));
236: }
237:
238: // private //////////////////////////////////////////////////////////////////////
239: private String getOSVersion() {
240: return System.getProperty("os.version");
241: }
242:
243: private File upToApp(final File file) {
244: File executable = file;
245:
246: while ((executable != null)
247: && !executable.getPath().endsWith(APP_SUFFIX)) {
248: executable = executable.getParentFile();
249: }
250:
251: return executable;
252: }
253:
254: private void modifyShortcutPath(final FileShortcut shortcut) {
255: if (shortcut.canModifyPath()) {
256: File target = upToApp(shortcut.getTarget());
257:
258: if (target != null) {
259: shortcut.setTarget(target);
260: }
261: }
262: }
263:
264: private boolean modifyDockLink(final FileShortcut shortcut,
265: final File dockFile, final boolean adding) {
266: OutputStream outputStream = null;
267: boolean modified = false;
268:
269: try {
270: if (shortcut instanceof FileShortcut) {
271: modifyShortcutPath(shortcut);
272:
273: final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
274: .newInstance();
275: documentBuilderFactory.setNamespaceAware(true);
276:
277: final DocumentBuilder documentBuilder = documentBuilderFactory
278: .newDocumentBuilder();
279: documentBuilder
280: .setEntityResolver(new PropertyListEntityResolver());
281: LogManager.log(ErrorLevel.DEBUG,
282: " parsing xml file...");
283:
284: final Document document = documentBuilder
285: .parse(dockFile);
286: LogManager.log(ErrorLevel.DEBUG, " ...complete");
287:
288: LogManager.log(ErrorLevel.DEBUG,
289: " getting root element");
290: final Element root = document.getDocumentElement();
291:
292: LogManager.log(ErrorLevel.DEBUG,
293: " getting root/dict element");
294: Element dict = XMLUtils.getChild(root, "dict");
295:
296: LogManager.log(ErrorLevel.DEBUG,
297: " getting root/dict/[key=persistent-apps] element. dict = "
298: + dict.getNodeName());
299: LogManager.log(ErrorLevel.DEBUG, "Get Keys");
300:
301: List<Element> keys = XMLUtils.getChildren(dict, "key");
302: LogManager.log(ErrorLevel.DEBUG, "Length = "
303: + keys.size());
304: Element persistentAppsKeyNode = null;
305: int index = 0;
306: while (keys.get(index) != null) {
307: if (keys.get(index).getTextContent().equals(
308: "persistent-apps")) {
309: persistentAppsKeyNode = keys.get(index);
310: break;
311: }
312: index++;
313: }
314:
315: if (persistentAppsKeyNode == null) {
316: LogManager.log(ErrorLevel.DEBUG,
317: " Not found.. strange.. Create new one");
318: persistentAppsKeyNode = XMLUtils.appendChild(dict,
319: "key", "persistent-apps");
320: } else {
321: LogManager.log(ErrorLevel.DEBUG,
322: " done. KeyNode = "
323: + persistentAppsKeyNode
324: .getTextContent());
325: }
326: LogManager
327: .log(ErrorLevel.DEBUG,
328: " Getting next element.. expecting it to be array element");
329: Element array = keys.get(index);
330: Node arrayIt = array;
331:
332: index = 0;
333: int MAX_SEARCH_ELEMENTS = 20;
334: String nodeName;
335: while (index < MAX_SEARCH_ELEMENTS && arrayIt != null) {
336: nodeName = arrayIt.getNodeName();
337: if (nodeName != null && nodeName.equals("array")
338: && arrayIt instanceof Element) {
339: break;
340: }
341:
342: arrayIt = arrayIt.getNextSibling();
343: index++;
344: }
345:
346: if (index == MAX_SEARCH_ELEMENTS || arrayIt == null) {
347: LogManager
348: .log(ErrorLevel.DEBUG,
349: " is not an array element... very strange");
350: return false;
351: }
352: array = (Element) arrayIt;
353:
354: if (array == null) {
355: LogManager.log(ErrorLevel.DEBUG,
356: " null... very strange");
357: return false;
358: }
359: LogManager.log(ErrorLevel.DEBUG, " OK. Content = "
360: + array.getNodeName());
361: if (!array.getNodeName().equals("array")) {
362: LogManager.log(ErrorLevel.DEBUG,
363: " Not an array element");
364: return false;
365: }
366:
367: if (adding) {
368: LogManager
369: .log(ErrorLevel.DEBUG,
370: "Adding shortcut with the following properties: ");
371:
372: LogManager.log(ErrorLevel.DEBUG, " target = "
373: + shortcut.getTargetPath());
374: LogManager.log(ErrorLevel.DEBUG, " name = "
375: + shortcut.getName());
376:
377: dict = XMLUtils.appendChild(array, "dict", null);
378: XMLUtils.appendChild(dict, "key", "tile-data");
379: Element dictChild = XMLUtils.appendChild(dict,
380: "dict", null);
381: XMLUtils.appendChild(dictChild, "key", "file-data");
382: Element dictCC = XMLUtils.appendChild(dictChild,
383: "dict", null);
384: XMLUtils.appendChild(dictCC, "key", "_CFURLString");
385: XMLUtils.appendChild(dictCC, "string", shortcut
386: .getTargetPath());
387: XMLUtils.appendChild(dictCC, "key",
388: "_CFURLStringType");
389: XMLUtils.appendChild(dictCC, "integer", "0");
390: XMLUtils
391: .appendChild(dictChild, "key", "file-label");
392: XMLUtils.appendChild(dictChild, "string", shortcut
393: .getName());
394: XMLUtils.appendChild(dictChild, "key", "file-type");
395: XMLUtils.appendChild(dictChild, "integer", "41");
396: XMLUtils.appendChild(dict, "key", "tile-type");
397: XMLUtils.appendChild(dict, "string", "file-tile");
398: LogManager.log(ErrorLevel.DEBUG,
399: "... adding shortcut to Dock XML finished");
400: modified = true;
401: } else {
402: LogManager
403: .log(ErrorLevel.DEBUG,
404: "Removing shortcut with the following properties: ");
405: LogManager.indent();
406: LogManager.log(ErrorLevel.DEBUG, " target = "
407: + shortcut.getTargetPath());
408: LogManager.log(ErrorLevel.DEBUG, "name = "
409: + shortcut.getName());
410:
411: String location = shortcut.getTargetPath();
412: List<Element> dcts = new LinkedList<Element>();
413:
414: for (Element el1 : XMLUtils.getChildren(array,
415: "dict")) {
416: for (Element el2 : XMLUtils.getChildren(el1,
417: "dict")) {
418: for (Element el3 : XMLUtils.getChildren(
419: el2, "dict")) {
420: dcts.addAll(XMLUtils.getChildren(el3,
421: "string"));
422: }
423: }
424: }
425:
426: index = 0;
427: Node dct = null;
428: LogManager.log(ErrorLevel.DEBUG,
429: "Total dict/dict/dict/string items = "
430: + dcts.size());
431: LogManager.log(ErrorLevel.DEBUG,
432: " location = " + location);
433:
434: File locationFile = new File(location);
435:
436: while (index < dcts.size()
437: && dcts.get(index) != null) {
438: Node item = dcts.get(index);
439: String content = item.getTextContent();
440: LogManager.log(ErrorLevel.DEBUG,
441: " content = " + content);
442: if (content != null && !content.equals("")) {
443: File contentFile = new File(content);
444: if (locationFile.equals(contentFile)) {
445: dct = item;
446: break;
447: }
448: }
449: index++;
450: }
451: ;
452:
453: if (dct != null) {
454: LogManager.log(ErrorLevel.DEBUG,
455: "Shortcut exists in the dock.plist");
456: array.removeChild(dct.getParentNode()
457: .getParentNode().getParentNode());
458: modified = true;
459: } else {
460: LogManager
461: .log(ErrorLevel.DEBUG,
462: "... shortcut doesn`t exist in the dock.plist");
463: modified = false;
464: }
465: LogManager.unindent();
466: LogManager
467: .log(ErrorLevel.DEBUG,
468: "... removing shortcut from Dock XML finished");
469: }
470: if (modified) {
471: LogManager.log(ErrorLevel.DEBUG,
472: " Saving XML... ");
473: XMLUtils.saveXMLDocument(document, dockFile);
474: LogManager.log(ErrorLevel.DEBUG,
475: " Done (saving xml)");
476: }
477: } else {
478: LogManager
479: .log(ErrorLevel.DEBUG,
480: "Adding non-file shortcuts to the Dock is not supported");
481: }
482: } catch (ParserConfigurationException e) {
483: LogManager.log(ErrorLevel.WARNING, e);
484: return false;
485: } catch (XMLException e) {
486: LogManager.log(ErrorLevel.WARNING, e);
487: return false;
488: } catch (SAXException e) {
489: LogManager.log(ErrorLevel.WARNING, e);
490: return false;
491: } catch (IOException e) {
492: LogManager.log(ErrorLevel.WARNING, e);
493: return false;
494: } catch (NullPointerException e) {
495: LogManager.log(ErrorLevel.WARNING, e);
496: return false;
497: } finally {
498: if (outputStream != null) {
499: try {
500: outputStream.close();
501: } catch (IOException ex) {
502: LogManager
503: .log(ErrorLevel.WARNING,
504: "Can`t close stream for Dock properties file");
505: }
506: }
507: }
508: LogManager.log(ErrorLevel.DEBUG, " Return " + modified
509: + " from modifyDockLink");
510: return modified;
511:
512: }
513:
514: private File getDockPropertiesFile() {
515: return new File(SystemUtils.getUserHomeDirectory(),
516: "Library/Preferences/" + DOCK_PROPERIES);//NOI18N
517: }
518:
519: private int convertDockProperties(boolean decode) {
520: File dockFile = getDockPropertiesFile();
521: int returnResult = 0;
522: try {
523: if (!isCheetah() && !isPuma()) {
524: if ((!decode && (isTiger() || isLeopard())) || decode) {
525: // decode for all except Cheetah and Puma
526: // code only for Tiger and Leopard
527:
528: ExecutionResults result = SystemUtils
529: .executeCommand(null, new String[] {
530: PLUTILS,
531: PLUTILS_CONVERT,
532: (decode) ? PLUTILS_CONVERT_XML
533: : PLUTILS_CONVERT_BINARY,
534: dockFile.getPath() });
535: returnResult = result.getErrorCode();
536: }
537: }
538: } catch (IOException ex) {
539: LogManager.log(ErrorLevel.WARNING);
540: returnResult = -1;
541: }
542: return returnResult;
543: }
544:
545: /////////////////////////////////////////////////////////////////////////////////
546: // Inner Classes
547: private class PropertyListEntityResolver implements EntityResolver {
548: public static final String PROPERTTY_LIST_DTD_LOCAL = "/System/Library/DTDs/PropertyList.dtd"; //NOI18N
549: public static final String PROPERTTY_LIST_DTD_REMOTE = "http://www.apple.com/DTDs/PropertyList-1.0.dtd"; //NOI18N
550: public static final String PROPERTTY_LIST_DTD_PUBLIC_ID = "-//Apple Computer//DTD PLIST 1.0//EN"; //NOI18N
551:
552: public InputSource resolveEntity(String publicId,
553: String systemId) throws SAXException, IOException {
554: File propDtd = new File(PROPERTTY_LIST_DTD_LOCAL);
555:
556: return ((PROPERTTY_LIST_DTD_PUBLIC_ID.equals(publicId) || PROPERTTY_LIST_DTD_REMOTE
557: .equals(systemId)) && FileUtils.exists(propDtd)) ? new InputSource(
558: new FileInputStream(propDtd))
559: : null;
560:
561: }
562:
563: }
564:
565: /////////////////////////////////////////////////////////////////////////////////
566: // Constants
567: public static final String LIBRARY_PATH_MACOSX = NATIVE_JNILIB_RESOURCE_SUFFIX
568: + "macosx/macosx.dylib"; // NO18N
569:
570: public static final String APP_SUFFIX = ".app"; // NOI18N
571:
572: private static final String DOCK_PROPERIES = "com.apple.dock.plist"; // NOI18N
573:
574: private static final String PLUTILS = "plutil"; // NOI18N
575:
576: private static final String PLUTILS_CONVERT = "-convert"; // NOI18N
577:
578: private static final String PLUTILS_CONVERT_XML = "xml1"; // NOI18N
579:
580: private static final String PLUTILS_CONVERT_BINARY = "binary1"; // NOI18N
581:
582: private static final String[] UPDATE_DOCK_COMMAND = new String[] {
583: "killall", // NOI18N
584: "-HUP", // NOI18N
585: "Dock", // NOI18N
586: };
587:
588: public static final String[] FORBIDDEN_DELETING_FILES_MACOSX = {
589: "/Applications", // NOI18N
590: "/Developer", // NOI18N
591: "/Library", // NOI18N
592: "/Network", // NOI18N
593: "/System", // NOI18N
594: "/Users", // NOI18N
595: };
596: }
|