package org.gudy.azureus2.platform.macosx.access.cocoa; /* * Created on 27-Mar-2005 * Created by James Yeh * Copyright (C) 2004-2005 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.AEThread; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.platform.macosx.NativeInvocationBridge; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.MessageFormat; /** * NOTE: This is only used for OSX 10.4 and non-up-to-date 10.5,10.6. * OSX 10.5 Update 6 and OSX 10.6 Update 1 provides revealInFinder and moveToTrash * functionality. * <p> * <p>Performs PlatformManager tasks using Cocoa-Java (FoundationKit only)</p> * <p>For now, operations are performed using NSAppleScript, rather than using NSWorkspace. * This is still significantly faster than calling the cmd-line osascript.</p> * @version 2.1 Apr 2, 2005 */ public final class CocoaJavaBridge extends NativeInvocationBridge { /** * The path the Cocoa-Java class files are located at */ protected static final String CLASS_PATH = "/system/library/java"; private static final String REVEAL_SCRIPT_FORMAT = "tell application \"System Events\"\ntell application \"{0}\"\nactivate\nreveal (posix file \"{1}\" as alias)\nend tell\nend tell"; private static final String DEL_SCRIPT_FORMAT = "tell application \"Finder\" to move (posix file \"{0}\" as alias) to the trash"; /** * Main NSAutoreleasePool */ private int mainPool; protected AEMonitor classMon = new AEMonitor("CocoaJavaBridge:C"); private AEMonitor scriptMon = new AEMonitor("CocoaJavaBridge:S"); protected boolean isDisposed = false; protected RunnableDispatcher scriptDispatcher; private Class claNSAppleEventDescriptor; private Class<?> claNSAutoreleasePool; private Method methPush; private Method methPop; private Method methNSAppleEventDescriptor_descriptorWithBoolean; private Class<?> claNSAppleScript; private Class<?> claNSMutableDictionary; private Method methNSAppleScript_execute; private String NSAppleScript_AppleScriptErrorMessage; private Method methNSMutableDictionary_objectForKey; public CocoaJavaBridge() throws Throwable { try { classMon.enter(); claNSMutableDictionary = Class.forName("com.apple.cocoa.foundation.NSMutableDictionary"); methNSMutableDictionary_objectForKey = claNSMutableDictionary.getMethod("objectForKey", Object.class); claNSAppleEventDescriptor = Class.forName("com.apple.cocoa.foundation.NSAppleEventDescriptor"); methNSAppleEventDescriptor_descriptorWithBoolean = claNSAppleEventDescriptor.getMethod("descriptorWithBoolean", new Class [] { boolean.class }); claNSAutoreleasePool = Class.forName("com.apple.cocoa.foundation.NSAutoreleasePool"); methPush = claNSAutoreleasePool.getMethod("push", new Class [0]); methPop = claNSAutoreleasePool.getMethod("pop", new Class [] { int.class }); claNSAppleScript = Class.forName("com.apple.cocoa.foundation.NSAppleScript"); methNSAppleScript_execute = claNSAppleScript.getMethod("execute", new Class[] { claNSMutableDictionary }); NSAppleScript_AppleScriptErrorMessage = (String) claNSAppleScript.getField("AppleScriptErrorMessage").get(null); //mainPool = NSAutoreleasePool.push(); mainPool = NSAutoreleasePool_push(); scriptDispatcher = new RunnableDispatcher(); } finally { classMon.exit(); } } private int NSAutoreleasePool_push() throws Throwable { return ((Number) methPush.invoke(null)).intValue(); } private void NSAutoreleasePool_pop(int i) throws Throwable { methPop.invoke(null, i); } private Object new_NSAppleScript(String s) throws Throwable { return claNSAppleScript.getConstructor(new Class[] { String.class }).newInstance(s); } private Object NSAppleScript_execute(Object NSAppleScript, Object NSMutableDictionary) throws Throwable { return methNSAppleScript_execute.invoke(NSAppleScript, NSMutableDictionary); } private Object new_NSMutableDictionary() throws Throwable { return claNSMutableDictionary.newInstance(); } private Object NSMutableDictionary_objectForKey(Object NSMutableDictionary, String s) throws Throwable { return methNSMutableDictionary_objectForKey.invoke(NSMutableDictionary, s); } // interface implementation /** * {@inheritDoc} */ protected boolean performRecoverableFileDelete(File path) { if(!path.exists()){ return false; } Object result; try { result = executeScriptWithAsync(DEL_SCRIPT_FORMAT, new Object[]{path.getAbsolutePath()}); } catch (Throwable t) { Debug.out(t); return false; } // quick hack here for people where things take a while - too scared to make it a // sync call as I don't know the code... if ( result != null ){ final int sleep = 25; int sleep_to_go = 2500; while( path.exists()){ if ( sleep_to_go <= 0 ){ break; } try{ Thread.sleep( sleep ); sleep_to_go -= sleep; }catch( Throwable e ){ break; } } if ( path.exists()){ Debug.outNoStack( "Gave up waiting for delete to complete for " + path ); } } return( result != null ); } /** * {@inheritDoc} */ protected boolean showInFinder(File path, String fileBrowserApp) { if (!path.exists()) return false; Object /*NSAppleEventDescriptor*/ result = null; try { int pool = NSAutoreleasePool_push(); try { result = executeScriptWithAsync(REVEAL_SCRIPT_FORMAT, new Object[] { fileBrowserApp, path.getAbsolutePath() }); } finally { NSAutoreleasePool_pop(pool); } } catch (Throwable t) { return false; } return (result != null); } /** * {@inheritDoc} */ protected boolean isEnabled() { return claNSAutoreleasePool != null; } // class utility methods /** * <p>Executes a new instance of NSAppleScript</p> * <p>The method is wrapped in an autorelease pool and an AEMonitor. If there are * no format parameters, MessageFormat is not used to parse the format string, and * the format string will be treated as the source itself.</p> * @see MessageFormat#format(String, Object...) * @see NSAppleScript#execute(com.apple.cocoa.foundation.NSMutableDictionary) */ protected final Object /*NSAppleEventDescriptor*/ executeScript(String scriptFormat, Object[] params) throws Throwable { try { scriptMon.enter(); int pool = NSAutoreleasePool_push(); long start = System.currentTimeMillis(); String src; if(params == null || params.length == 0) { src = scriptFormat; } else { src = MessageFormat.format(scriptFormat, params); } Debug.outNoStack("Executing: \n" + src); Object /*NSAppleScript*/ scp = new_NSAppleScript(src); Object /*NSAppleEventDescriptor*/ result = NSAppleScript_execute(scp, new_NSMutableDictionary()); Debug.outNoStack(MessageFormat.format("Elapsed time: {0}ms\n", new Object[]{new Long(System.currentTimeMillis() - start)})); NSAutoreleasePool_pop(pool); return result; } finally { scriptMon.exit(); } } /** * <p>Executes a new instance of NSAppleScript in a forked AEThread</p> * <p>This method always returns a "true" event descriptor. Callbacks are currently unsupported * , so in the event of an error, the logger is autuomatically notified.</p> * <p>The thread's runSupport method is wrapped in an autorelease pool. If there are * no format parameters, MessageFormat is not used to parse the format string, and * the format string will be treated as the source itself.</p> * @see org.gudy.azureus2.core3.util.AEThread#runSupport() * @see MessageFormat#format(String, Object...) * @see NSAppleScript#execute(com.apple.cocoa.foundation.NSMutableDictionary) * @return NSAppleEventDescriptor.descriptorWithBoolean(true) * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ protected final Object /*NSAppleEventDescriptor*/ executeScriptWithNewThread( final String scriptFormat, final Object[] params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { Thread worker = new AEThread("ScriptObject", true) { public void runSupport() { try { int pool = NSAutoreleasePool_push(); long start = System.currentTimeMillis(); String src; if (params == null || params.length == 0) { src = scriptFormat; } else { src = MessageFormat.format(scriptFormat, params); } Debug.outNoStack("Executing: \n" + src); Object /*NSMutableDictionary*/ errorInfo = new_NSMutableDictionary(); if (NSAppleScript_execute(new_NSAppleScript(src), errorInfo) == null) { Debug.out(String.valueOf(NSMutableDictionary_objectForKey(errorInfo, NSAppleScript_AppleScriptErrorMessage))); //logWarning(String.valueOf(errorInfo.objectForKey(NSAppleScript.AppleScriptErrorBriefMessage))); } Debug.outNoStack(MessageFormat.format("Elapsed time: {0}ms\n", new Object[] { new Long(System.currentTimeMillis() - start) })); NSAutoreleasePool_pop(pool); } catch (Throwable e) { Debug.out(e); } } }; worker.setPriority(Thread.NORM_PRIORITY - 1); worker.start(); return methNSAppleEventDescriptor_descriptorWithBoolean.invoke(null, true); //return NSAppleEventDescriptor.descriptorWithBoolean(true); } /** * <p>Asynchronously executes a new instance of NSAppleScript</p> * <p>This method always returns a "true" event descriptor. Callbacks are currently unsupported * , so in the event of an error, the logger is autuomatically notified.</p> * <p>The thread's runSupport method is wrapped in an autorelease pool. If there are * no format parameters, MessageFormat is not used to parse the format string, and * the format string will be treated as the source itself.</p> * @see org.gudy.azureus2.core3.util.AEThread#runSupport() * @see MessageFormat#format(String, Object...) * @see NSAppleScript#execute(com.apple.cocoa.foundation.NSMutableDictionary) * @return NSAppleEventDescriptor.descriptorWithBoolean(true) * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ protected final Object /*NSAppleEventDescriptor*/ executeScriptWithAsync(final String scriptFormat, final Object[] params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { final AERunnable worker = new AERunnable() { public void runSupport() { try { int pool = NSAutoreleasePool_push(); long start = System.currentTimeMillis(); String src; if (params == null || params.length == 0) { src = scriptFormat; } else { src = MessageFormat.format(scriptFormat, params); } Debug.outNoStack("Executing: \n" + src); Object /*NSMutableDictionary*/ errorInfo = new_NSMutableDictionary(); if (NSAppleScript_execute(new_NSAppleScript(src), errorInfo) == null) { Debug.out(String.valueOf(NSMutableDictionary_objectForKey(errorInfo, NSAppleScript_AppleScriptErrorMessage))); //logWarning(String.valueOf(errorInfo.objectForKey(NSAppleScript.AppleScriptErrorBriefMessage))); } Debug.outNoStack(MessageFormat.format("Elapsed time: {0}ms\n", new Object[] { new Long(System.currentTimeMillis() - start) })); NSAutoreleasePool_pop(pool); } catch (Throwable t) { Debug.out(t); } } }; AEThread t = new AEThread("ScriptObject", true) { public void runSupport() { scriptDispatcher.exec(worker); } }; t.setPriority(Thread.NORM_PRIORITY - 1); t.start(); return methNSAppleEventDescriptor_descriptorWithBoolean.invoke(null, true); //return NSAppleEventDescriptor.descriptorWithBoolean(true); } /** * Logs a warning message to Logger. The class monitor is used. * @param message A warning message */ private void logWarning(String message) { try { classMon.enter(); Logger.log(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_WARNING, message)); } finally { classMon.exit(); } } // disposal /** * {@inheritDoc} */ protected void dispose() { try { classMon.enter(); if(!isDisposed) { Debug.outNoStack("Disposing Native PlatformManager..."); try { NSAutoreleasePool_pop(mainPool); } catch (Throwable e) { } isDisposed = true; Debug.outNoStack("Done"); } } finally { classMon.exit(); } } /** * {@inheritDoc} */ protected void finalize() throws Throwable { dispose(); super.finalize(); } /** * A dispatch object to help facilitate asychronous script execution (from the main thread) in a more * predictable fashion. */ private static class RunnableDispatcher { /** * Executes a Runnable object while synchronizing the RunnableDispatcher instance. * @param runnable A Runnable */ private void exec(Runnable runnable) { synchronized(this) { runnable.run(); } } } }