/*
 * @(#)ProcessJAR.java	1.15 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media.customizer;

import java.util.*;
import java.util.zip.*;
import java.io.*;
import javax.swing.JProgressBar;
import javax.media.PlugInManager;
import javax.media.CaptureDeviceManager;
import javax.media.CaptureDeviceInfo;
//import javax.media.cdm.*;


/**
  * The purpose of this class is to do the real customization work:
  *         open the source JMF JAR file,
  *         filter out those plugins not selected by the user,
  *         create the target JAR file and write out all necessary classes.
  *   An object of this class will run on a separate thread.
  * @version 2.0
  *
  */

public class ProcessJAR extends Thread implements TokenDef {
    public static final String[] sollibs = {
	"libCvidPro.so",
	"libjmcvid.so",
	"libjmdaud.so",
	"libjmfCVIDPro.so",
	"libjmg723.so",
	"libjmgsm.so",
	"libjmh261.so",
	"libjmh263enc.so",
	"libjmiv32.so",
	"libjmjpeg.so",
	"libjmmpa.so",
	"libjmmpegv.so",
	"libjmmpx.so",
	"libjmmpx2.so",
	"libjmopi.so",
	"libjmsunray.so",
	"libjmvh263.so",
	"libjmxil.so",
	"libjmxlib.so",
	"libjsound.so",
	"soundbank.gm",
	"sound.jar",
    };

    public static final String[] win32libs = {
	"jmacm.dll",
	"jmam.dll",
	"jmcvid.dll",
	"jmdaud.dll",
	"jmdaudc.dll",
	"jmddraw.dll",
	"jmg723.dll",
	"jmgdi.dll",
	"jmgsm.dll",
	"jmh261.dll",
	"jmh263enc.dll",
	"jmiv32.dll",
	"jmjpeg.dll",
	"jmmci.dll",
	"jmmpa.dll",
	"jmmpegv.dll",
	"jmvcm.dll",
	"jmvfw.dll",
	"jmvh263.dll",
	"jsound.dll",
	"soundbank.gm",
	"sound.jar",
    };

    public static final Hashtable cdmap;
    static {
	cdmap = new Hashtable();
	
	cdmap.put("dsound", "com/sun/media/protocol/dsound/DataSource.class");
	cdmap.put("javasound", "com/sun/media/protocol/javasound/DataSource.class");
	cdmap.put("vfw", "com/sun/media/protocol/vfw/DataSource.class");
	cdmap.put("sunvideo", "com/sun/media/protocol/sunvideo/DataSource.class");
	cdmap.put("sunvideoplus", "com/sun/media/protocol/sunvideoplus/DataSource.class");
    }
    
    String srcJARname;
    String dstJARname;
    String workDir;
    int[] selected;
    ProgressDiag progressDlg;
    CusRegistry theRegistry;
    int release;
    boolean twojars;
    
    public ProcessJAR(String srcJARname, String dstJARname, int[] selected,ProgressDiag progressDlg, CusRegistry theRegistry, int release, boolean twojars) {
	this.srcJARname = srcJARname;
	this.dstJARname = dstJARname;
	this.selected = selected;
	this.progressDlg = progressDlg;
	this.theRegistry = theRegistry;
	this.workDir = this.theRegistry.getWorkDir();
	this.release = release;
	this.twojars = twojars;
    }
  
    
    /**
     * run():
     *   The method of the thread to do the real job:
     *
     *       Open source JAR file:
     *           Open it for reading
     *           make sure all mediaFormat/Codecs classes are there to pick
     *           display class name/class count/file size while processing
     *
     *       Process the request by marking necessary MediaFormat/Codec classes
     *
     *       Create destinaton JAR file:
     *           create the file,  abort if any error
     *           for each class from source
     *             if this class is needed, write it to the destination JAR/ZIP file
     *             otherwise ignore it
     *
     *       When done, enable DONE button of the progress bar
     *
     * @Param N/A
     * @return N/A
     *
     */
    public void run() {
	// Assuming 35% for reading, 15% for updating the registry and 50% for writing, can be adjusted if needed.
	File srcFile, dstFile, dstFile2 = null;
	long srcfilesize, filesizeSoFar;
	int classCountSoFar;
	int progressSoFar = 0;
	ZipFile srcZF = null;
	ZipEntry srcZE;
	
	FileOutputStream fos, fos2;
	ZipOutputStream zos = null, zos2 = null;
	InputStream is = null;
	
	double readRate, writeRate;
	//System.out.println("in ProcessJAR run...");
	//System.out.println("srcname = " + srcJARname);
	//System.out.println("dstname = " + dstJARname);
	//for ( int i = 0; i < selected.length; i++)
	//System.out.println(" " + selected[i]);
	
	// let dialog thread execute first
	try {
	    sleep(30);
	} catch (Exception ex) {
	}
	
	srcFile = new File(srcJARname);
	
	// We will use the file size as the base for the initial segment
	// of the progress bar
	srcfilesize = srcFile.length();
	readRate = 35.0/srcfilesize;
	
	// Open the source file as a ZipFile
	try {
	    srcZF = new ZipFile(srcFile);
	}
	catch (ZipException e) {
	    errorMessage(e.getMessage());
	    return;
	}
	catch (IOException e) {
	    errorMessage(e.getMessage());
	    return;
	}
	
	if ( CustomDB.getSize() == 0 ) 
	    CustomDB.buildDB();
	else 
	    CustomDB.clearAllMarks();
	
	Vector orglist = new Vector();
	Enumeration se = srcZF.entries();
	String clsname;
	long entrysize;
	
	classCountSoFar = 0;
	filesizeSoFar = 0;
	
	// read classes from source jar
	while (se.hasMoreElements()) {
	    srcZE = (ZipEntry)se.nextElement();
	    
	    clsname = srcZE.getName();
	    if (!(clsname.endsWith(".class") || clsname.endsWith(".gif")) ){
		// System.out.println(clsname);
		continue;
	    }
	    orglist.addElement(srcZE);
	    
	    entrysize = srcZE.getCompressedSize();
	    classCountSoFar++;
	    filesizeSoFar += entrysize;
	    
	    if ( classCountSoFar % 5 == 0 ) {
		progressSoFar = (int)(readRate*filesizeSoFar);
		progressDlg.updateValue(progressSoFar);
		progressDlg.updateSourceInfo(filesizeSoFar, classCountSoFar);
		myyield();
	    }
	}
	
	
	progressDlg.updateSourceInfo(srcfilesize, classCountSoFar);
	myyield();
	
	// Finish loading source jar file, start to write target
	int notwanted = CustomDB.markAllClasses(selected);
	writeRate = 50.0/(classCountSoFar - notwanted);
	
	progressDlg.updateNote(I18N.getResource("ProcessJAR.REGISTRY"));
	myyield();
	
	updateRegistry();
	
	// Create the destination file
	if ( !twojars ) {
	    dstFile = new File(dstJARname);
	    try {
		fos = new FileOutputStream(dstFile);
		zos = new ZipOutputStream(fos);
	    } catch (IOException e) {
		try {
		    srcZF.close();
		}
		catch (IOException ex) {
		}
		dstFile.delete();
		errorMessage(e.getMessage());
		return;
	    }
	} else {
	    dstFile = new File(dstJARname);
	    String pp = dstFile.getParent() + File.separator;
	    String fn = dstFile.getName();
	    // System.out.println("pp = " + pp);
	    // System.out.println("fn = " + fn);
	    
	    dstFile = new File(pp + "core_" + fn);
	    dstFile2 = new File(pp + "plugin_" + fn);
	    try {
		fos = new FileOutputStream(dstFile);
		fos2 = new FileOutputStream(dstFile2);
		zos = new ZipOutputStream(fos);
		zos2 = new ZipOutputStream(fos2);
	    } catch (IOException e) {
		try {
		    srcZF.close();
		}
		catch (IOException ex) {
		}
		dstFile.delete();
		dstFile2.delete();
		errorMessage(e.getMessage());
		return;
	    }
	}

	classCountSoFar = 0;
	filesizeSoFar = 0;
	byte buffer[] = new byte[4096];  // Read/Write buffer
	int len;
	DBItem dbentry;
	
	progressDlg.updateNote(I18N.getResource("ProcessJAR.WRT"));
	myyield();
	
	// Traverse all classes from source JAR/ZIp file
	int NNN = orglist.size();
	if ( !twojars ) {
	    for (int nn = 0 ; nn < NNN; nn++ ) {
		srcZE = (ZipEntry)(orglist.elementAt(nn));
		
		clsname = srcZE.getName();
		entrysize = srcZE.getCompressedSize();
		
		if ( (dbentry=(DBItem)CustomDB.get(clsname)) == null || dbentry.isMarked()) { 
		    ++classCountSoFar;
		    // filesizeSoFar += entrysize;
		    ZipEntry newZE;
		    String rClsName = "com/sun/media/util/RegistryLib.class";
		    
		    try {
			if ( clsname.equals(rClsName)) {
			    String fullName = rClsName.replace('/', File.separatorChar);
			    fullName = workDir + File.separator + fullName;
			    is = new FileInputStream(fullName);
			} else {
			    is = srcZF.getInputStream(srcZE);
			}
			
			newZE = new ZipEntry(clsname);
			zos.putNextEntry(newZE);
			while (true) {
			    if (is.available() == 0)
				break;
			    len = is.read(buffer);
			    if (len == -1)
				break;
			    zos.write(buffer, 0, len);
			}
			zos.closeEntry();
			is.close();
			if (clsname.equals("com/sun/media/util/RegistryLib.class")) {
			    System.out.println("Default Registry replaced");
			    entrysize = newZE.getCompressedSize();
			}
		    } catch (IOException e) {
			try {
			    srcZF.close();
			}catch (IOException ex) {
			}
			
			dstFile.delete();
			errorMessage(e.getMessage());
			return;
		    }
		    
		    filesizeSoFar += entrysize;
		}
		
		if ( classCountSoFar % 5 == 0 ) {
		    progressSoFar = (int)(50 + writeRate * classCountSoFar);
		    progressDlg.updateValue(progressSoFar);
		    progressDlg.updateTargetInfo(filesizeSoFar, classCountSoFar);
		    myyield();
		}
		
	    } // end of for nn
	 		
	    // Done
	    try {
		zos.close();
		srcZF.close();
	    } catch ( IOException ex) {
		dstFile.delete();
		errorMessage(ex.getMessage());
		return;
	    }
	    
	} else { // generate two jars
	    for (int nn = 0 ; nn < NNN; nn++ ) {
		srcZE = (ZipEntry)(orglist.elementAt(nn));
		
		clsname = srcZE.getName();
		entrysize = srcZE.getCompressedSize();
		dbentry=(DBItem)CustomDB.get(clsname);

		if ( dbentry == null ) { // core classes
		    ++classCountSoFar;
		    // filesizeSoFar += entrysize;
		    ZipEntry newZE;
		    
		    try {
			is = srcZF.getInputStream(srcZE);
						
			newZE = new ZipEntry(clsname);
			zos.putNextEntry(newZE);
			while (true) {
			    if (is.available() == 0)
				break;
			    len = is.read(buffer);
			    if (len == -1)
				break;
			    zos.write(buffer, 0, len);
			}
			zos.closeEntry();
			is.close();
		    } catch (IOException e) {
			try {
			    srcZF.close();
			}catch (IOException ex) {
			}
			
			dstFile.delete();
			errorMessage(e.getMessage());
			return;
		    }
		    
		} else if ( dbentry.isMarked()) { // plugable classes
		    ++classCountSoFar;
		    // filesizeSoFar += entrysize;
		    ZipEntry newZE;
		    String rClsName = "com/sun/media/util/RegistryLib.class";
		    
		    try {
			if ( clsname.equals(rClsName)) {
			    String fullName = rClsName.replace('/', File.separatorChar);
			    fullName = workDir + File.separator + fullName;
			    is = new FileInputStream(fullName);
			} else {
			    is = srcZF.getInputStream(srcZE);
			}
			
			newZE = new ZipEntry(clsname);
			zos2.putNextEntry(newZE);
			while (true) {
			    if (is.available() == 0)
				break;
			    len = is.read(buffer);
			    if (len == -1)
				break;
			    zos2.write(buffer, 0, len);
			}
			zos2.closeEntry();
			is.close();
			if (clsname.equals("com/sun/media/util/RegistryLib.class")) {
			    System.out.println("Default Registry replaced");
			    entrysize = newZE.getCompressedSize();
			}
		    } catch (IOException e) {
			try {
			    srcZF.close();
			}catch (IOException ex) {
			}
			
			dstFile2.delete();
			errorMessage(e.getMessage());
			return;
		    }

		}
		
		filesizeSoFar += entrysize;

		if ( classCountSoFar % 5 == 0 ) {
		    progressSoFar = (int)(50 + writeRate * classCountSoFar);
		    progressDlg.updateValue(progressSoFar);
		    progressDlg.updateTargetInfo(filesizeSoFar, classCountSoFar);
		    myyield();
		}
		
	    }
	    
	
	    // Done
	    try {
		zos.close();
		zos2.close();
		srcZF.close();
	    } catch ( IOException ex) {
		dstFile.delete();
		dstFile2.delete();
		errorMessage(ex.getMessage());
		return;
	    }
	} // end of else twojars

	// ??
	long dstfilesize = dstFile.length();
	if ( twojars ) {
	    dstfilesize += dstFile2.length();
	}
	progressDlg.updateValue(100);
	progressDlg.updateTargetInfo(dstfilesize, classCountSoFar);
	progressDlg.enableDone();
	
	// Process native libs
	if ( release >= 2 ) {
	    System.out.println("***Needed native modules***");
	}
	
	if ( release == 2 ) { //SPP
	    String libname = null;
	    for (int i = 0; i < sollibs.length; i++) {
		libname = sollibs[i];
		if ( (dbentry=(DBItem)CustomDB.get(libname)) == null || dbentry.isMarked()) { 
		    System.out.println(libname);
		}
	    }
	    System.out.println("libjmutil.so");
	}
	
	if (release == 3 ) {
	    String libname = null;
	    for (int i = 0; i < win32libs.length; i++) {
		libname = win32libs[i];
		if ( (dbentry=(DBItem)CustomDB.get(libname)) == null || dbentry.isMarked()) { 
		    System.out.println(libname);
		}
	    }
	    System.out.println("jmutil.dll");
	}
	
	
    }
    
    
    private void errorMessage(String msg) {
	progressDlg.sentErr(msg);
	myyield();
    }
    
    private void myyield() {
	try {
	    sleep(15);
	} catch (Exception ex) {
	}
    }
    

    private void updateRegistry() {
	Vector v;
	Hashtable newDefHash, newFullHash, theHash;
	boolean b_def, b_full;
	int len;
	String pin, newpin;
	
	
	newFullHash = (Hashtable)(theRegistry.getFullRegistry().clone());
	if ( newFullHash != null && newFullHash.size() > 0 )
	    b_full = true;
	else
	    b_full = false;

	if ( b_full ) {
	    newDefHash = newFullHash;
	    b_def = false;
	} else {
	    newDefHash = (Hashtable)(theRegistry.getDefaultRegistry().clone());
	    
	    if ( newDefHash != null && newDefHash.size() > 0 )
		b_def = true;
	    else
		b_def = false;
	}

	// update DEMUX
	v = PlugInManager.getPlugInList(null, null, PlugInManager.DEMULTIPLEXER);
	len = v.size();
	
	for (int i = 0; i < len; i++) {
	    pin = (String)v.elementAt(i);
	    pin.trim();
	    newpin = pin.replace('.', '/') + ".class";
	    DBItem dbentry = (DBItem)CustomDB.get(newpin);
	    if ( dbentry != null && !dbentry.isMarked()) {
		if ( b_full) {
		    theRegistry.removePlugIn(newFullHash, pin,PlugInManager.DEMULTIPLEXER); 
		} else if ( b_def) {
		    theRegistry.removePlugIn(newDefHash, pin,PlugInManager.DEMULTIPLEXER); 
		}
	    }
	}
	
	// update CODEC
	v = PlugInManager.getPlugInList(null, null, PlugInManager.CODEC);
	len = v.size();
	
	for (int i = 0; i < len; i++) {
	    pin = (String)v.elementAt(i);
	    pin.trim();
	    newpin = pin.replace('.', '/') + ".class";
	    DBItem dbentry = (DBItem)CustomDB.get(newpin);
	    if ( dbentry != null && !dbentry.isMarked()) {
		// PlugInManager.removePlugIn(pin,PlugInManager.CODEC);
		if ( b_full) {
		    theRegistry.removePlugIn(newFullHash,pin,PlugInManager.CODEC);
		} else if ( b_def) {
		    theRegistry.removePlugIn(newDefHash,pin,PlugInManager.CODEC);
		}
	    }
	}
	// update MUX
	v = PlugInManager.getPlugInList(null, null, PlugInManager.MULTIPLEXER);
	len = v.size();
	
	for (int i = 0; i < len; i++) {
	    pin = (String)v.elementAt(i);
	    pin.trim();
	    newpin = pin.replace('.', '/') + ".class";
	    DBItem dbentry = (DBItem)CustomDB.get(newpin);
	    if ( dbentry != null && !dbentry.isMarked()){
		// PlugInManager.removePlugIn(pin,PlugInManager.MULTIPLEXER);
		if ( b_full) {
		    theRegistry.removePlugIn(newFullHash, pin,PlugInManager.MULTIPLEXER); 
		} else if ( b_def) {
		    theRegistry.removePlugIn(newDefHash, pin,PlugInManager.MULTIPLEXER);
		}
	    }
	}

	// update RENDERER
	v = PlugInManager.getPlugInList(null, null, PlugInManager.RENDERER);
	len = v.size();
	
	for (int i = 0; i < len; i++) {
	    pin = (String)v.elementAt(i);
	    pin.trim();
	    newpin = pin.replace('.', '/') + ".class";
	    DBItem dbentry = (DBItem)CustomDB.get(newpin);
	    if ( dbentry != null && !dbentry.isMarked()){ 
		// PlugInManager.removePlugIn(pin,PlugInManager.RENDERER);
		if (b_full) { 
		    theRegistry.removePlugIn(newFullHash, pin,PlugInManager.RENDERER);
		} else if (b_def) {
		    theRegistry.removePlugIn(newDefHash, pin,PlugInManager.RENDERER);
		}
	    }
	}
	
	// update Capture Devices
	v = CaptureDeviceManager.getDeviceList(null);
	len = v.size();
	String dname = null;

	for ( int i = 0; i < len; i++){
	    CaptureDeviceInfo info = (CaptureDeviceInfo)v.elementAt(i);
	    dname = info.getLocator().getProtocol();
	    
	    if ( !isCDSelected(dname)) {
		if ( b_full) { 
		    theRegistry.removeCaptureD(newFullHash, dname);
		} else if (b_def) {
		    theRegistry.removeCaptureD(newDefHash, dname);
		}
	    }
	} 

	
	// generate RegistyLib.java
	if ( b_full ) {
	    writeRegistryLib(newFullHash) ;
	} else if ( b_def) {
	    writeRegistryLib(newDefHash);
	}
	
	// compile RegistryLib
	try {
	    String[] cmdarr = new String[4];
	    cmdarr[0] = theRegistry.getJavacPath() + File.separator + "javac";
	    cmdarr[1] = "-d";
	    cmdarr[2] = workDir;
	    cmdarr[3] = workDir + File.separator + "RegistryLib.java";
	    
	    Process proc = Runtime.getRuntime().exec(cmdarr);
	    proc.waitFor();
	} catch (Exception ex) {
	    ex.printStackTrace();
	}

	// save the full registry to disk
	if (release >= 2 && b_full)
	    theRegistry.saveRegistry(newFullHash);

	progressDlg.updateValue(42);
	myyield();
	
    }
    
    private boolean isCDSelected(String dname) {
	DBItem dbentry = null;
	String pname;

	// System.out.println("dname = " + dname);
	pname = (String)(cdmap.get(dname));
	dbentry = (DBItem)CustomDB.get(pname);

	if ( dbentry != null && dbentry.isMarked())
	    return true;
	
	return false;
    }
    
    private void writeRegistryLib(Hashtable newhash) {
	byte[] properties;
	int i,j;
	
	properties = null;
	
	// write the newhash to a ByteOutputStream
	try {
	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
	    ObjectOutputStream oos = new ObjectOutputStream(bos);
	    int tableSize = newhash.size();
	    oos.writeInt(tableSize);
	    oos.writeInt(theRegistry.versionNumber);
	    for (Enumeration e = newhash.keys(); e.hasMoreElements() ;) {
		String key = (String) e.nextElement();
		Object value = newhash.get(key);
		
		oos.writeUTF(key);    // write key as UTF chars.
		
		oos.writeObject(value);
		oos.flush();
	    }
	    oos.close();
	    properties = bos.toByteArray();
	} catch (Exception ex) {
	    ex.printStackTrace();
	    System.out.println("Sth is wrong when saving the registry");
	    return;
    }
	
	
	// write RegistryLib
	try {
	    String registryname = workDir + File.separator + "RegistryLib.java";
	    DataOutputStream ds = new DataOutputStream(new FileOutputStream(registryname));
	    ds.writeBytes("/* Generated by RegistryGen.\n   DO NOT EDIT.*/\n\n");
	    ds.writeBytes("package com.sun.media.util;\n\n");
	    ds.writeBytes("public abstract class ");
	    ds.writeBytes("RegistryLib");
	    ds.writeBytes(" {\n\n");
	    if (properties.length > 0) {
		ds.writeBytes("   public static byte[] getData(){\n");
		ds.writeBytes("       int i;\n");
		ds.writeBytes("       byte[] b= new byte["+properties.length+"];\n");
		ds.writeBytes("       for (i=0;i<b.length;i++)\n");
		// switch back between 255 and 0
		ds.writeBytes("          b[i] = (byte)(s.charAt(i)-1);\n");
		ds.writeBytes("       return b;\n");
		ds.writeBytes("    }\n");
	    } else {
		ds.writeBytes("   public static byte[] getData(){\n");
		ds.writeBytes("       return null;\n");
		ds.writeBytes("    }\n");
	    }
	    
	    ds.writeBytes("    private static String s = \n        ");
	    ds.writeBytes("\"");
	    int len= properties.length;
	    for (j = 0; j < len; j++) {
		// switch between 255 and 0 since 0 is more common
		ds.writeBytes( ("\\"+byte2oct((byte)(1+properties[j]))) );
		if ((j%16)==15) {
		    ds.writeBytes("\"+\n        \""  );
		}
	    }
	    ds.writeBytes("\";\n\n"  );
	    ds.writeBytes("}\n"); //trailer
	    ds.close();
	} catch (Exception ex) {
	    System.out.println("Sth wrong when writing RegsitryLib");
	}
	
  }
    
  
    // convert byte to its octal presentation (always 3 characters)
    private static String byte2oct(byte b) {
	int i=b&0xff;
	int dig3=i%8;
	int dig2=(i/8)%8;
	int dig1=i/64;
    return (""+dig1+""+dig2+""+dig3);
    }
}



    

