/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package netscape.plugin.composer; import java.util.*; import java.awt.image.ImageProducer; import java.awt.image.MemoryImageSource; import java.io.*; import java.net.URL; import netscape.javascript.JSObject; /* This class is private to the composer. It manages the plugins. */ class PluginManager { public PluginManager(){ } public void registerPlugin(String pluginFileName, String iniContents){ Properties properties = new Properties(); File pluginFile = new File(pluginFileName); try { InputStream stream = new StringBufferInputStream(iniContents); properties.load(stream); stream.close(); } catch ( IOException e) { System.err.println("Caught exception while parsing .ini contents:\n" + iniContents); e.printStackTrace(); } registerPlugins(pluginFile, properties); registerEncoders(pluginFile, properties); registerEvents(properties); } public void registerPlugins(File pluginFile, Properties properties){ Enumeration plugins = null; String factoryName = null; try { factoryName = trimWhitespace(properties.getProperty("netscape.plugin.composer.Factory", "netscape.plugin.composer.Factory")); Factory factory = (Factory) Class.forName(factoryName).newInstance(); plugins = factory.getPlugins(pluginFile, properties); } catch ( Throwable t ) { System.err.println("Caught exception while instantiating " + factoryName); t.printStackTrace(); } registerPlugins(categories, plugins); } public static Enumeration instantiateClassList(String classNames){ Vector result = new Vector(); if ( classNames != null ) { StringTokenizer tokenizer = new StringTokenizer(classNames, ":"); while( tokenizer.hasMoreTokens() ){ String className = PluginManager.trimWhitespace(tokenizer.nextToken()); try { Object object = Class.forName(className).newInstance(); result.addElement(object); } catch (Throwable t) { System.err.println("Caught exception while instantiating " + className); t.printStackTrace(); } } } return result.elements(); } protected void registerEvents(Properties properties){ String eventHandlers = properties.getProperty("netscape.plugin.composer.eventHandlers", ""); // System.err.println("registerEvents " + eventHandlers); registerPlugins(events, eventHandlers); } protected static void registerPlugins(SortedStringTable table, String classNames) { registerPlugins(table, instantiateClassList(classNames)); } protected static void registerPlugins(SortedStringTable table, Enumeration plugins){ if ( plugins != null ) { while(plugins.hasMoreElements()){ Plugin plugin = (Plugin) plugins.nextElement(); if ( plugin != null ) { try { table.getOrCreateTable(plugin.getCategory()).put( plugin.getName(), plugin); } catch(Throwable t){ System.err.println("Trouble registering plugin:" + plugin); t.printStackTrace(); } } } } } public static String trimWhitespace(String string){ int length = string.length(); StringBuffer out = new StringBuffer(length); for(int i = 0; i < length; i++ ){ char c = string.charAt(i); if ( !Character.isSpace(c) ) { out.append(c); } } return out.toString(); } public void registerEncoders(File pluginFile, Properties properties){ Enumeration encoderList = null; String factoryName = null; try { factoryName = trimWhitespace(properties.getProperty("netscape.plugin.composer.ImageEncoderFactory", "netscape.plugin.composer.ImageEncoderFactory")); ImageEncoderFactory factory = (ImageEncoderFactory) Class.forName(factoryName).newInstance(); encoderList = factory.getImageEncoders(pluginFile, properties); } catch ( Throwable t ) { System.err.println("Caught exception while instantiating " + factoryName); t.printStackTrace(); } if ( encoderList != null ) { while(encoderList.hasMoreElements()){ Object object = null; try { object = encoderList.nextElement(); ImageEncoder encoder = (ImageEncoder) object; registerEncoderInstance(encoder); } catch ( Throwable t ) { System.err.println("Caught exception while processing " + object); t.printStackTrace(); } } } } private void registerEncoderInstance(ImageEncoder encoder){ ImageEncoder.register(encoder); String name = encoder.getName(); encoders.put(name, encoder); } public int getNumberOfCategories(){ return categories.length(); } public int getNumberOfPlugins(int category){ SortedStringTable categoryTable = (SortedStringTable) categories.get(category); if ( categoryTable == null ) { return 0; } return categoryTable.length(); } public String getCategoryName(int category){ return categories.getKey(category); } public String getPluginName(int type, int index){ return getPlugin(type, index).getName(); } public String getPluginHint(int type, int index){ return getPlugin(type, index).getHint(); } /** Start a plugin operation. */ public boolean performPlugin(Composer composer, int category, int index, String in, String base, String workDirectory, String workDirectoryURL, JSObject jsobject){ return performPlugin2(composer, getPlugin(category, index), in, base, workDirectory, workDirectoryURL, null, null,jsobject); } public boolean performPluginByName(Composer composer, String pluginName, String in, String base, String workDirectory, String workDirectoryURL, JSObject jsobject){ Plugin plugin = null; String destinationURL = null; String eventName = null; try { int eventMarker = pluginName.indexOf(':'); if ( eventMarker >= 0 ) { eventName = pluginName.substring(0,eventMarker); destinationURL = pluginName.substring(eventMarker + 1); if ( destinationURL.length() == 0 ) { destinationURL = null; } SortedStringTable table = events; // System.err.println("Executing plug-ins for event " + eventName + " " + table); if ( table != null ) { plugin = new GroupPlugin(table); } } else { plugin = (Plugin) Class.forName(pluginName).newInstance(); } if ( plugin != null ) { return performPlugin2(composer, plugin, in, base, workDirectory, workDirectoryURL, eventName, destinationURL, jsobject); } } catch(Throwable t){ t.printStackTrace(); } return false; } public boolean performPlugin2(Composer composer, Plugin plugin, String in, String base, String workDirectory, String workDirectoryURL, String eventName, String destinationURL, JSObject jsobject){ String result = in; if ( plugin != null ){ try { URL baseURL = base != null ? new URL(base) : null; URL workDirectoryURLURL = null; URL destinationURLURL = null; if ( workDirectoryURL != null ) { String slash = "/"; // The workDirectoryURL needs to end in a "/" so that // relative URLs work. if ( ! workDirectoryURL.endsWith(slash) ){ workDirectoryURL = workDirectoryURL + slash; } // Netscape on Windows likes urls of the form // file:///C|/temp/ // But this freaks out the URL parser in java. // Try to work around this by matching the file: // part. String prefix = "file:"; if ( workDirectoryURL.indexOf(prefix) == 0 ){ workDirectoryURLURL = new URL("file","", workDirectoryURL.substring(prefix.length())); } else { workDirectoryURLURL = new URL(workDirectoryURL); } //System.err.println("Source: " + workDirectoryURL); //System.err.println("Simple result: " + workDirectoryURLURL ); //System.err.println("URL(file,,///C|/temp) = "+ new URL("file","","///C|/temp")); //System.err.println("Relative URL: " + new URL(workDirectoryURLURL, "Floop.jpg")); } if ( destinationURL != null ) { // Netscape on Windows likes urls of the form // file:///C|/temp/ // But this freaks out the URL parser in java. // Try to work around this by matching the file: // part. String prefix = "file:"; if ( destinationURL.indexOf(prefix) == 0 ){ destinationURLURL = new URL("file","", destinationURL.substring(prefix.length())); } else { destinationURLURL = new URL(destinationURL); } } ComposerDocument document = new ComposerDocument(composer, in, baseURL, workDirectory, workDirectoryURLURL, eventName, destinationURLURL, jsobject); SecurityManager.enablePrivilege("SuperUser"); ThreadGroup threadGroup = new ThreadGroup( Thread.currentThread().getThreadGroup(), plugin.getName()); Thread thread = new Thread(threadGroup, new PluginRunner(plugin, document, this), plugin.getName()); thread.start(); pluginThreads.put(composer, threadGroup); } catch (IOException e) { System.err.println("Composer plugin runner threw this exception:"); e.printStackTrace(); return false; } } else { return false; } return true; } /** Stop a plugin operation */ public void stopPlugin(Composer composer){ killGroup(composer); } public int getNumberOfEncoders(){ return encoders.length(); } public String getEncoderName(int index){ return getEncoder(index).getName(); } public String getEncoderFileType(int index){ byte[] fileType = new byte[4]; getEncoder(index).getFileType(fileType); return new String(fileType, 0); } public boolean getEncoderNeedsQuantizedSource(int index){ return getEncoder(index).needsQuantizedSource(); } public String getEncoderFileExtension(int index){ return getEncoder(index).getFileExtension(); } public String getEncoderHint(int index){ return getEncoder(index).getHint(); } protected ImageEncoder getEncoder(int index) { return (ImageEncoder) encoders.get(index); } /** Start an encoder operation. */ public boolean startEncoder(Composer composer, int index, int width, int height, byte[][] pixels, String fileName){ Plugin encoderPlugin = new ImageEncoderPlugin(getEncoder(index), width, height, pixels, fileName); return performPlugin2(composer, encoderPlugin, null, null, null, null, null, null,null); } protected Plugin getPlugin(int category, int index){ Plugin result = null; SortedStringTable categoryTable = (SortedStringTable) categories.get(category); if ( categoryTable != null ) { result = (Plugin) categoryTable.get(index); } return result; } void pluginFinished(Composer composer, int status, Object argument){ composer.pluginFinished(status, argument); killGroup(composer); } void killGroup(Composer composer){ ThreadGroup group = (ThreadGroup) pluginThreads.remove(composer); if ( group != null ) { pluginKiller.kill(group); } } private SortedStringTable categories = new SortedStringTable(); private SortedStringTable encoders = new SortedStringTable(); private SortedStringTable events = new SortedStringTable(); private Hashtable pluginThreads = new Hashtable(); // Composer composer, Thread private PluginKiller pluginKiller = new PluginKiller(); } class PluginRunner implements Runnable { public PluginRunner(Plugin plugin, ComposerDocument document, PluginManager manager){ this.plugin = plugin; this.document = document; this.manager = manager; } public void run() { Composer composer = document.getComposer(); // Take this out someday when security manager works better. // SecurityManager.enablePrivilege("SuperUser"); try { boolean result = plugin.perform(document); manager.pluginFinished(composer, result ? Composer.PLUGIN_OK : Composer.PLUGIN_CANCEL, null); } catch( ThreadDeath t) { System.err.println("Composer plugin " + plugin + " was killed."); } catch (Throwable t){ System.err.println("Composer plugin " + plugin + " threw this exception:"); t.printStackTrace(); manager.pluginFinished(composer, Composer.PLUGIN_FAIL, t.toString()); } } private Plugin plugin; private ComposerDocument document; private PluginManager manager; } class ImageEncoderPlugin extends Plugin { public ImageEncoderPlugin(ImageEncoder encoder, int width, int height, byte[][] pixels, String fileName) { this.encoder = encoder; this.width = width; this.height = height; this.pixels = pixels; this.fileName = fileName; } public boolean perform(Document document) throws IOException { // Create the image. int length = width * height; int[] pixels2 = new int[length]; int i = 0; for(int y = 0; y < height; y++) { int j = 0; byte[] line = pixels[y]; for(int x = 0; x < width; x++ ) { pixels2[i++] = (0xff << 24) | ((0xff & line[j]) << 16) | ((0xff & line[j+1]) << 8) | ((0xff & line[j+2])); j += 3; } } ImageProducer source = new MemoryImageSource(width, height, pixels2, 0, width); ByteArrayOutputStream temp = new ByteArrayOutputStream(width*height/2); boolean result = encoder.encode(source, temp); if ( result ) { // Create the output SecurityManager.enablePrivilege("SuperUser"); OutputStream output = new FileOutputStream(new File(fileName)); output.write(temp.toByteArray()); output.close(); } return result; } private ImageEncoder encoder; private int width; private int height; private byte[][] pixels; private String fileName; } /** Processes a sequence of plugins. */ class GroupPlugin extends Plugin { public GroupPlugin(SortedStringTable table){ table_ = table; } public boolean perform(Document document) throws IOException { // Perform each of the plug-ins in order boolean result = true; // System.err.println("GroupPlugin ready to go! " + table_); if ( table_ != null ) { Enumeration categories = table_.values(); while ( categories.hasMoreElements() && result ){ try { SortedStringTable category = (SortedStringTable) categories.nextElement(); Enumeration plugins = category.values(); while ( plugins.hasMoreElements() && result ){ Plugin plugin = null; try { plugin = (Plugin) plugins.nextElement(); result = plugin.perform(document); } catch(Throwable t){ System.err.print("Exception while executing " + plugin); t.printStackTrace(); result = false; } } } catch(Throwable t){ t.printStackTrace(); result = false; } } } return result; } SortedStringTable table_; } class PluginKiller implements Runnable { PluginKiller() { list = new Vector(); // Temporary security work-around for pre-4.0PR3 SecurityManager.enablePrivilege("SuperUser"); Thread killer = new Thread(this, "Composer Plug-in Killer"); killer.start(); } synchronized void kill(ThreadGroup group) { list.addElement(group); notifyAll(); } synchronized ThreadGroup getPluginThreadGroup() { while (list.isEmpty()) { try { wait(); } catch (InterruptedException e) { } } int last = list.size() - 1; ThreadGroup threadGroup = (ThreadGroup) list.elementAt(last); list.removeElementAt(last); return threadGroup; } public void run() { while (true) { // this thread cannot die for any reason. // Catch any exception and keep killing try { ThreadGroup group = getPluginThreadGroup(); synchronized (group) { //System.err.println("killing threadgroup " + group); // Give the thread some time to exit. If it exits // sooner then we will be notified when the group runs // out of threads. try { group.wait(300); } catch (InterruptedException e) { } // Check and see if thread is really gone if (group.activeCount() > 0) { // Gun down the remaining threads. try { SecurityManager.enablePrivilege("SuperUser"); group.stop(); group.wait(5000); } catch (InterruptedException e) { } finally { SecurityManager.revertPrivilege(); } } // Finally, destroy the thread group try { SecurityManager.enablePrivilege("SuperUser"); group.destroy(); } catch (Exception e) { } finally { SecurityManager.revertPrivilege(); } } } catch (Exception e) { System.out.println("Exception occurred while deleting Composer Plug-in thread group: "); e.printStackTrace(); } } } public String toString() { return new String("Plugin thread killer! Composer plugins to be disposed of: " + list); } // the list of composer plug-in groups to finish private Vector list; // Of ThreadGroup }