/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.autoupdate;

import java.net.URL;
import java.net.URLConnection;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.jar.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import org.openide.DialogDisplayer;

import org.w3c.dom.*;
import org.xml.sax.InputSource;

import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.modules.ModuleInfo;
import org.openide.util.NbBundle;

/** This class represents one module update available on the web
 *
 * @author  phrebejk
 * @version 
 */
class ModuleUpdate extends Object
    implements org.openide.nodes.Node.Cookie {
    
    // Constants
    private static final String ATTR_HOMEPAGE = "homepage"; // NOI18N
    private static final String ATTR_DISTRIBUTION = "distribution"; // NOI18N
    private static final String ATTR_CODENAMEBASE = "codenamebase"; // NOI18N
    private static final String ATTR_DOWNLOAD_SIZE = "downloadsize"; // NOI18N
    private static final String ATTR_UNPACKED_SIZE = "unpacksize"; // NOI18N
    private static final String ATTR_LICENSE = "license"; // NOI18N
    private static final String ATTR_LICENSE_MISSPELLED = "licence"; // NOI18N
    private static final String ATTR_PURCHASED = "purchased"; // NOI18N
    private static final String ATTR_NEEDSRESTART = "needsrestart"; // NOI18N
    
    private static final String ATTR_AUTHOR = "moduleauthor"; // NOI18N
    private static final String ATTR_RELEASE_DATE = "releasedate"; // NOI18N
    private static final String ATTR_IS_GLOBAL = "global"; // NOI18N
    
    static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat ("yyyy/MM/dd"); // NOI18N

    private static final String ELEMENT_DESCRIPTION = "description"; // NOI18N
    private static final String ELEMENT_NOTIFICATION = "module_notification"; // NOI18N
    private static final String ELEMENT_EXTERNAL = "external_package"; // NOI18N
    static final String ELEMENT_L10N = "l10n"; // NOI18N

    private static final String ATTR_EXT_NAME = "name"; // NOI18N
    private static final String ATTR_EXT_TARGET = "target_name"; // NOI18N
    private static final String ATTR_EXT_URL = "start_url"; // NOI18N
    private static final String ATTR_EXT_DESC = "description"; // NOI18N
        
    // special directories in NB files layout
    static final String NBM_LIB = "lib"; // NOI18N
    static final String NBM_CORE = "core"; // NOI18N

    //private static LicenceCache licenceCache = new LicenceCache();

    /** The base url of XML Document used to create URL from relative paths */
    private URL xmlURL;
    /** Node in the DOM the ModuleUpdate comes from */
    private Node node;
    /** Holds the document of the XML */
    private Element documentElement;

    /** Used for downloaded files */
    private File nbmFile;


    /** Holds value of property distribution. */
    private URL distribution = null;
    /** Holds value of property homepage. */
    private URL homepage = null;
    /** Holds value of property infoCodenamebase. */
    private String infoCodenamebase = null;
    /** Holds value of property downloadSize. */
    private long downloadSize = -1;
    /** Holds value of property unpackedSize. */
    private long unpackedSize = -1;
    /** Holds value of property downlaodOK. */
    private boolean downloadOK = false;
    /** Holds value of property description. */
    private String description = null;
    /** Holds value of property licence */
    private String licenceID = null;
    /** Holds value text of the licence */
    private String licenceText = null;
    /** Holds value of property selected */
    private boolean selected = false;
    /** Holds value of property secutiry */
    private int security = SignVerifier.NOT_CHECKED;
    /** Holds value of property certificates */
    private Collection certs = null;
    /** Holds value of property installApproved */
    private boolean installApproved = false;

    // Associations
    private ModuleInfo localModule = null;

    private ModuleInfo remoteModule = null;
    
    /** Holds value of property purchased. */
    private boolean purchased = false;
    
    /** Holds value of property notification. */
    private String notification = null;
    
    /** Holds value of property notification. */
    private String moduleAuthor = null;
    
    /** Holds value of property notification. */
    private Date releaseDate = null;
    
    /** Was notification accepted? */
    private boolean notificationAccepted = false;

    private String distributionFilename = null;
    
    /** Holds value of property external. */
    private List externals;
    
    /** Holds value of property depending. */
    private boolean depending;
    
    /** Holds value of property toInstallDir. */
    private boolean toInstallDir;
    
    /** Holds value of property downloadStarted. */
    private boolean downloadStarted = false;
    
    /** Holds value of property safeToInstall. */
    private boolean safeToInstall = false;
    
    /** Holds value of property isGlobal. */
    private Boolean isGlobal = null;
    
    /** Holds value of property jarList. */
    private List jarList = new ArrayList();
    
    //private Updates linearStructure;

    // CONSTRUCTORS -------------------------------------------------------------

    /** Creates new ModuleUpdate */
    ModuleUpdate( URL xmlURL, Node node, Element documentElement ) {
        this.xmlURL = xmlURL;
        this.node = node;
        this.documentElement = documentElement;
    }

    /** Creates new ModuleUpdate for downloaded .nbm file */
    ModuleUpdate( File nbmFile, Node node, Element documentElement ) {
        this.nbmFile = nbmFile;
        this.node = node;
        this.documentElement = documentElement;
    }

    static ModuleUpdate getModuleUpdate( URL xmlURL, Node node, Element documentElement, AutoupdateType at ) {
        ModuleUpdate mod;
        NodeList nodeList = ((Element)node).getElementsByTagName( ELEMENT_L10N ); // NOI18N
        if ( nodeList != null && nodeList.getLength() > 0 )
            mod = new L10NUpdate( xmlURL, node, documentElement );
        else
            mod = new ModuleUpdate( xmlURL, node, documentElement );
        
        if ( mod.readModuleUpdate( at ) )
            return mod;
        else
            return null;
    }
    
    static private String getMessage (String key, String p1) {
        return NbBundle.getMessage (ModuleUpdate.class, key, p1);
    }
    
    static ModuleUpdate getModuleUpdate( File nbmFile ) {
        ModuleUpdate mod;
        Document document = null;
        Node node;
        Element documentElement;
        
        // Try to parse the info file
        JarFile jf = null;
        try {
            jf = new JarFile(nbmFile);
            ZipEntry info = getLocalizedInfo( jf ); // NOI18N
            if ( info == null ) {
                DialogDisplayer.getDefault().notify( 
                    new NotifyDescriptor.Message(
                        NbBundle.getMessage( ModuleUpdate.class, "MSG_NoInfoXml",
                            nbmFile.getName()
                        ),
                        NotifyDescriptor.ERROR_MESSAGE
                    )
                );
                return null;
            }
            InputStream is = jf.getInputStream( info );
            
            InputSource xmlInputSource = new InputSource( is );
            document = org.openide.xml.XMLUtil.parse( xmlInputSource, false, false,
                            new ErrorCatcher(), org.netbeans.updater.XMLUtil.createAUResolver() );
            
            documentElement = document.getDocumentElement();
            node = documentElement;
        } catch ( org.xml.sax.SAXException e ) {
            ErrorManager.getDefault ().annotate (e, ErrorManager.UNKNOWN,
                    "Bad info : " + nbmFile.getName(), // NOI18N
                    getMessage ("ERR_Bad_Info", nbmFile.getName ()), null, null); // NOI18N
            ErrorManager.getDefault ().notify (ErrorManager.WARNING, e);
            return null;
        } catch ( ZipException ze ) {
            ErrorManager.getDefault ().annotate (ze, ErrorManager.ERROR,
                    "Corrupted nbm file : " + nbmFile.getName(), // NOI18N
                    getMessage ("ERR_Corrupted_Zip", nbmFile.getName ()), null, null); // NOI18N
            ErrorManager.getDefault ().notify (ErrorManager.WARNING, ze);
            return null;
        } catch (UnknownHostException e) {
            String message = "Cannot access required resource (see the message log for more details). " // NOI18N
                + "Module " + nbmFile.getName() + " will be ommited in the install list."; // NOI18N
            ErrorManager.getDefault().annotate(e, ErrorManager.EXCEPTION,
                    message,
                    getMessage("ERR_Unavailability_Resource", nbmFile.getName()), // NOI18N
                    null,
                    null);
            ErrorManager.getDefault().notify(ErrorManager.ERROR, e);
            return null;
        } catch ( IOException e ) {
            ErrorManager.getDefault ().annotate (e, ErrorManager.UNKNOWN,
                    "Missing info : " + nbmFile.getName (), // NOI18N
                    getMessage ("ERR_Missing_Info", nbmFile.getName ()), null, null); // NOI18N
            ErrorManager.getDefault ().notify (ErrorManager.WARNING, e);
            return null;
        } finally {
            try {
                if ( jf != null )
                    jf.close();
            } catch ( IOException ie ) {                
            }
        }
        
        NodeList nodeList = ((Element)node).getElementsByTagName( ELEMENT_L10N ); // NOI18N
        if ( nodeList != null && nodeList.getLength() > 0 )
            mod = new L10NUpdate( nbmFile, node, documentElement );
        else
            mod = new ModuleUpdate( nbmFile, node, documentElement );
        
        if ( mod.createFromDistribution() )
            return mod;
        else
            return null;
    }
    
    // METHODS ------------------------------------------------------------------

    /** Reads the module update from DOM and if the module is already loaded
     * finds the description 
     * @return True if the read-operation was O.K.
     */
    boolean readModuleUpdate(AutoupdateType at) {

        // Read module update information

        try {
            String textURL = getAttribute( ATTR_HOMEPAGE );
            if ( textURL != null )
                homepage = new URL( xmlURL, textURL );
        }
        catch ( java.net.MalformedURLException e ) {
            ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
            // let homepage set to null
        }


        
        setInfoCodenamebase( getAttribute( ATTR_CODENAMEBASE ) );

        try {
            setDownloadSize( Long.parseLong( getAttribute( ATTR_DOWNLOAD_SIZE ) ) );
        }
        catch ( NumberFormatException e ) {
            // Let the value set to -1
        }

        try {
            setUnpackedSize( Long.parseLong( getAttribute( ATTR_UNPACKED_SIZE ) ) );
        }
        catch ( NumberFormatException e ) {
            // Let the value set to -1
        }

        setDescription( getTextOfElement( ELEMENT_DESCRIPTION ) );
        
        setNotification( getTextOfElement( ELEMENT_NOTIFICATION ) );
        
        List ext = externalFromXML( );
        if ( ext != null )
            externals = ext;

        try {
            String textURL = getAttribute( ATTR_DISTRIBUTION );
            if ( textURL != null ) {
                String sURL = xmlURL.toString();
                int qmark = sURL.indexOf('?');
                if ((qmark > -1) && (qmark < sURL.lastIndexOf('/')))
                    // hack, when properties includes slash
                    distribution = new URL ( new URL(sURL.substring(0, qmark)), textURL );                
                else
                    distribution = new URL ( xmlURL, textURL );
                if ( at instanceof XMLAutoupdateType )                       
                    distribution = ((XMLAutoupdateType)at).modifyURL (distribution);
            }
        }
        catch ( java.net.MalformedURLException e ) {
            ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
            // let distibution URL set to null
        }

        purchased = Boolean.valueOf( getAttribute( ATTR_PURCHASED ) ).booleanValue();

        processNeedsRestart();
        processIsGlobal ();
        
        readReleaseDate ();
        readModuleAuthor ();
        
        try {
            remoteModule = readRemoteInfo();
        }
        catch ( IllegalArgumentException e ) {
            ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
        }

        // Read the licence from the XML
        licenceID = getAttribute( ATTR_LICENSE );
	if (licenceID == null) licenceID = getAttribute( ATTR_LICENSE_MISSPELLED );
        licenceText = licenseFromXML( licenceID, documentElement );

        // Try to find installed module
        localModule = readLocalInfo();

        return remoteModule != null;
    }

    ModuleInfo readRemoteInfo() throws IllegalArgumentException {
        // Read the manifest from XML file and create
        // the module description
        Manifest mf = manifestFromXML( );

        // See above.
        if (description == null || description.equals ("")) { // NOI18N
            String longDesc = mf.getMainAttributes ().getValue ("OpenIDE-Module-Long-Description"); // NOI18N
            if (longDesc != null) {
                description = longDesc;
            }
        }

        return new DummyModuleInfo( mf.getMainAttributes() );
    }
    
    ModuleInfo readLocalInfo() {
        ModuleInfo localinfo = null;
        
        ModuleInfo[] installedModules = Updates.getInstalledModules();
        ModuleInfo[] installedPatches = Updates.getInstalledPatches();

        if ( remoteModule != null ) {

            // Try if the module describes the Core IDE
            if ( remoteModule.getCodeName().equals( IdeDescription.getName() ) )  {
                localinfo = IdeDescription.getIdeDescription();
            }

            // Try other modules
            for ( int i = 0; localModule == null && i < installedModules.length; i++ ) {
                if ( installedModules[i].getCodeNameBase().equals( remoteModule.getCodeNameBase() ) ) {
                    localinfo = installedModules[i];
                    break;
                }
            }

            // Try wether the module is a installed patch
            for ( int i = 0; localModule == null && i < installedPatches.length; i++ ) {
                if ( installedPatches[i].getCodeNameBase().equals( remoteModule.getCodeNameBase() ) ) {
                    localinfo = installedPatches[i];
                    break;
                }
            }
        }
        return localinfo;
    }
    
    private static ZipEntry getLocalizedInfo(JarFile jf) {
        String locale = Locale.getDefault().getLanguage();
        ZipEntry info = jf.getEntry("Info/locale/info_" + locale + ".xml"); // NOI18N
        if ( info == null ) {
            info = jf.getEntry("Info/info.xml"); // NOI18N
        }
        return info;
    }

    /** Creates module from downloaded .nbm file */
    private boolean createFromDistribution() {

        Document document = null;
        
        // Try to parse the info file
        JarFile jf = null;
        try {
            jf = new JarFile(nbmFile);
            ZipEntry info = getLocalizedInfo( jf ); // NOI18N
            if ( info == null ) {
                DialogDisplayer.getDefault().notify( 
                    new NotifyDescriptor.Message(
                        NbBundle.getMessage( ModuleUpdate.class, "MSG_NoInfoXml",
                            nbmFile.getName()
                        ),
                        NotifyDescriptor.ERROR_MESSAGE
                    )
                );
                return false;
            }
            InputStream is = jf.getInputStream( info );
            
            InputSource xmlInputSource = new InputSource( is );
            document = org.openide.xml.XMLUtil.parse( xmlInputSource, false, false,
                            new ErrorCatcher(), org.netbeans.updater.XMLUtil.createAUResolver() );
            
            documentElement = document.getDocumentElement();
            node = documentElement;
        }
        catch ( org.xml.sax.SAXException e ) {
            ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN, "Bad info : " + nbmFile.getName(), null, null, null); // NOI18N
            ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
            return false;
        }
        catch ( IOException e ) {
            ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN, "Missing info : " + nbmFile.getName(), null, null, null); // NOI18N
            ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
            return false;
        }
        finally {
            try {
                if ( jf != null )
                    jf.close();
            } catch ( IOException ie ) {                
            }
        }

        // Read module update information

        try {
            String textURL = getAttribute( ATTR_HOMEPAGE );
            if ( textURL != null && textURL.length () > 0 ) {
                homepage = new URL( textURL );
            }
        }
        catch ( java.net.MalformedURLException e ) {
            ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
            // let homepage set to null
        }

        setInfoCodenamebase( getAttribute( ATTR_CODENAMEBASE ) );

        setDownloadSize( nbmFile.length() );


        /*
        try {
          setDownloadSize( Long.parseLong( getAttribute( ATTR_DOWNLOAD_SIZE ) ) );
    }
        catch ( NumberFormatException e ) {
          // Let the value set to -1
    }

        try {
          setUnpackedSize( Long.parseLong( getAttribute( ATTR_UNPACKED_SIZE ) ) );
    }
        catch ( NumberFormatException e ) {
          // Let the value set to -1
    }
        */

        setDescription( getTextOfElement( ELEMENT_DESCRIPTION ) );
        
        setNotification( getTextOfElement( ELEMENT_NOTIFICATION ) );
        
        List ext = externalFromXML( );
        if ( ext != null )
            externals = ext;

        purchased = Boolean.valueOf( getAttribute( ATTR_PURCHASED ) ).booleanValue();
        
        processNeedsRestart();
        processIsGlobal ();
        
        readReleaseDate ();
        readModuleAuthor ();
        
        try {
            remoteModule = readRemoteInfo();
        }
        catch ( IllegalArgumentException e ) {
            ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
        }
        
        // Read the licence from the XML
        licenceID = getAttribute( ATTR_LICENSE );
	if (licenceID == null) licenceID = getAttribute( ATTR_LICENSE_MISSPELLED );
        licenceText = licenseFromXML( licenceID, documentElement );


        // Try to find installed module
        localModule = readLocalInfo();
        
        return remoteModule != null;

    }

    /** Finds the module in the Colloection of installed modules */
    void resolveInstalledModule( Collection installedModules ) {

    }
    
    private void readReleaseDate () {
        String attr = getAttribute (ATTR_RELEASE_DATE);
        
        if (attr != null) {
            try {
                setReleaseDate (DATE_FORMAT.parse (attr));
            } catch (ParseException pe) {
                // notify in log
                ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, pe);
            }
        }
    }
    
    private void readModuleAuthor () {
        setModuleAuthor (getAttribute (ATTR_AUTHOR));
    }

    private void processNeedsRestart() {
        String attr = getAttribute( ATTR_NEEDSRESTART );
        if ( attr != null && Boolean.FALSE.toString().equals( attr.toLowerCase() ) )
            setSafeToInstall( true );
    }
    
    private void processIsGlobal () {
        String attr = getAttribute( ATTR_IS_GLOBAL );
        if (attr != null) {
            if (Boolean.TRUE.toString ().equals (attr.toLowerCase ())) {
                isGlobal = Boolean.TRUE;
            } else if (Boolean.FALSE.toString ().equals (attr.toLowerCase ())) {
                isGlobal = Boolean.FALSE;
            }
        }
    }
    
    // GETTERS AND SETTERS ------------------------------------------------------

    /** Getter for property codeNameBase.
     *@return Value of property codeNameBase.
     */
    String getCodeNameBase() {
        return remoteModule.getCodeNameBase();
    }

    /** Getter for property name.
     *@return Value of property name.
     */
    String getName() {
        return remoteModule.getDisplayName();
    }

    /** Getter for property distribution.
     *@return Value of property distribution.
     */
    URL getDistribution() {
        return distribution;
    }

    void setRemoteDistributionFilename(URLConnection distrConnection) {
        if ( distributionFilename == null ) {
            distrConnection.getContentType();
            String s = distrConnection.getURL().getFile();            
            int i = s.indexOf('?');
            if ( i > 0 )
                s = s.substring(0,i);
            i = s.lastIndexOf('/');
            if ( (i > 0) && (i < s.length()-1) )
                s = s.substring(i+1);            
            distributionFilename = s;
        }
    }
    
    /** Lazy getter for property distributionFilename.
     *@return Name of the distribution file.
     */
    String getDistributionFilename() {
        if ( distributionFilename == null ) {
            if ( nbmFile != null ) {
                distributionFilename = nbmFile.getName();
            }
            else {
                try {
                    URLConnection distrConnection = getDistribution().openConnection();
                    setRemoteDistributionFilename(distrConnection);
                } catch (IOException e) {
                    distributionFilename = new File( getDistribution().getFile() ).getName();
                }
            }
        }
        return distributionFilename;
    }

    /** Getter for property licenceID.
     *@return Value of property licenceID.
     */
    String getLicenceID() {
        return licenceID;
    }

    /** Getter for property infoCodenamebase.
     *@return Value of property infoCodenamebase.
     */
    public String getInfoCodenamebase() {
        return infoCodenamebase;
    }

    /** Setter for property infoCodenamebase.
     *@param InfoCodenamebase New value of property infoCodenamebase.
     */
    void setInfoCodenamebase(String infoCodenamebase) {
        this.infoCodenamebase = infoCodenamebase;
    }

    /** Getter for property downloadSize.
     *@return Value of property downloadSize.
     */
    long getDownloadSize() {
        return downloadSize;
    }

    /** Setter for property downloadSize.
     *@param downloadSize New value of property downloadSize.
     */
    void setDownloadSize(long downloadSize) {
        this.downloadSize = downloadSize;
    }

    /** Getter for property unpackedSize.
     *@return Value of property unpackedSize.
     */
    long getUnpackedSize() {
        return unpackedSize;
    }

    /** Setter for property unpackedSize.
     *@param unpackedSize New value of property unpackedSize.
     */
    void setUnpackedSize(long unpackedSize) {
        this.unpackedSize = unpackedSize;
    }

    /** Getter for property description.
     *@return Value of property description.
     */
    String getDescription() {
        return description;
    }

    /** Setter for property description.
     *@param description New value of property description.
     */
    void setDescription(String description) {
        this.description = description;
    }

    /** Getter for property module author.
     *@return Value of property moduleAuthor.
     */
    String getModuleAuthor () {
        return moduleAuthor;
    }

    /** Setter for property moduleAuthor.
     *@param notification New value of property moduleAuthor.
     */
    void setModuleAuthor (String author) {        
        moduleAuthor = author;
    }
    
    /** Getter for property releaseDate.
     *@return Value of property releaseDate.
     */
    Date getReleaseDate () {
        return releaseDate;
    }

    /** Setter for property releaseDate.
     *@param notification New value of property releaseDate.
     */
    void setReleaseDate (Date date) {        
        releaseDate = date;
    }
    
    /** Getter for property notification.
     *@return Value of property notification.
     */
    String getNotification() {
        return notification;
    }

    /** Setter for property notification.
     *@param notification New value of property notification.
     */
    void setNotification(String notification) {        
        this.notification = notification;        
    }
    
    /** Getter for property notificationAccepted.
     *@return Value of property notificationAccepted.
     */
    boolean getNotificationAccepted() {
        return notificationAccepted;
    }
    
    /** Setter for property notificationAccepted.
     *@param notificationAccepted New value of property notificationAccepted.
     */
    void setNotificationAccepted(boolean notificationAccepted) {
        this.notificationAccepted = notificationAccepted;        
    }
    
    /** Getter for property selected.
     *@return Value of property selected.
     */
    boolean isSelected() {
        return selected;
    }

    /** Setter for property selected.
     *@param description New value of property selected.
     */
    void setSelected( boolean selected ) {
        this.selected = selected;
    }


    /** Getter for property new.
     *@return True if such module is not installed in netbeans
     */
    boolean isNew() {
        return localModule == null;
    }
    
    /** Getter for property purchased.
     *@return True if such module is not installed in netbeans
     */
    boolean isPurchased() {
        return purchased;
    }

    /** Getter for property homePage.
     *@return Value of property homePage.
     */
    URL getHomePage() {
        return homepage;
    }

    /** Getter for property licenceText.
     *@return Value of property licenceText.
     */
    String getLicenceText() {
        return licenceText;
    }

    /** Getter for property remoteModule.
     *@return Value of property remoteModule.
     */
    ModuleInfo getRemoteModule() {
        return remoteModule;
    }
    
    /** Getter for property localModule.
     *@return Value of property localModule.
     */
    ModuleInfo getLocalModule() {
        return localModule;
    }
    
    Node getNode() {
        return node;
    }

    /** Tests if there is an update available */
    boolean isUpdateAvailable() {
        if ( getLocalModule() == null )
            return true;

        if ( getRemoteModule().getCodeNameRelease() > getLocalModule().getCodeNameRelease() ) {
            return true;
        }

        if (getLocalModule().getSpecificationVersion() == null) {
            return true;
        }
        if (getRemoteModule().getSpecificationVersion() == null) {
            return false;
        }
        if (getLocalModule().getSpecificationVersion().compareTo(getRemoteModule().getSpecificationVersion()) < 0) {
            return true;
        }

        return false;

    }

    /** Returns true if file wasn't checked still.
     */
    boolean isNotChecked() {
        return security == SignVerifier.NOT_CHECKED;
    }
    
    /** Getter for property downloadOK.
     *@return Value of property downloadOK.
     */
    boolean isDownloadOK() {
        return downloadOK;
    }

    /** Setter for property downloadOK
     *@param downloadOK New value of property downloadOK.
     */
    void setDownloadOK( boolean downloadOK ) {
        this.downloadOK = downloadOK;
    }


    /** Getter for property security.
     *@return Value of property security.
     */
    int getSecurity() {
        return security;
    }

    /** Setter for property security
     *@param downloadOK New value of property security.
     */
    void setSecurity( int security ) {
        this.security = security;
    }

    /** Getter for property certificates.
     *@return Value of property certificates.
     */
    Collection getCerts() {
        return certs;
    }

    /** Setter for property certificates
     *@param downloadOK New value of property certificates.
     */
    void setCerts( Collection certs ) {
        this.certs = certs;
    }


    /** Getter for property nbmFile.
     *@return The manually downloaded nbm file.
     */
    File getDistributionFile() {
        return nbmFile;
    }

    /** Getter for property installApproved.
     *@return Value of property installApproved.
     */
    boolean isInstallApproved() {
        return installApproved;
    }

    /** Setter for property installApproved.
     *@param description New value of property installApproved.
     */
    void setInstallApproved( boolean installApproved ) {
        this.installApproved = installApproved;
    }

    // UTILITY METHODS ----------------------------------------------------------

    /** Utility method gets the atribute of node
     *@param attribute Name of the desired attribute
     */
    private String getAttribute(String attribute) {
        Node attr = node.getAttributes().getNamedItem( attribute );
        return attr == null ? null : attr.getNodeValue();
    }

    /** Utility method gets text of subelement. Used for getting
     * description text.
     *@param name Name of the desired subelement
     */
    private String getTextOfElement( String name ) {

        if ( node.getNodeType() != Node.ELEMENT_NODE ||
                !( node instanceof Element ) ) {
            return null;
        }

        NodeList nodeList = ((Element)node).getElementsByTagName( name );
        StringBuffer sb = new StringBuffer();

        for( int i = 0; i < nodeList.getLength(); i++ ) {

            if ( nodeList.item( i ).getNodeType() != Node.ELEMENT_NODE ||
                    !( nodeList.item( i ) instanceof Element ) ) {
                break;
            }

            // ((Element)nodeList.item( i )).normalize();
            NodeList innerList = nodeList.item( i ).getChildNodes();

            for( int j = 0; j < innerList.getLength(); j++ ) {
		short type = innerList.item( j ).getNodeType();
                if ( type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE )  {
                    sb.append( innerList.item( j ).getNodeValue() );
                }
            }
        }
        return sb.toString();
    }

    /** Utility methods finds the external_package tag, reads all it's attributes and
     * creates the manifest */
    private List externalFromXML() {
        if ( node.getNodeType() != Node.ELEMENT_NODE ||
                !( node instanceof Element ) ) {
            return null;
        }
        List ext_list = null;

        NodeList nodeList = ((Element)node).getElementsByTagName( ELEMENT_EXTERNAL ); // NOI18N

        for( int i = 0; i < nodeList.getLength(); i++ ) {
            
            if ( i==0 )
                ext_list = new ArrayList();

            if ( nodeList.item( i ).getNodeType() != Node.ELEMENT_NODE ||
                    !( nodeList.item( i ) instanceof Element ) ) {
                break;
            }

            // ((Element)nodeList.item( i )).normalize();
            NamedNodeMap attrList = nodeList.item( i ).getAttributes();
            External ext = new External();
            
            for( int j = 0; j < attrList.getLength(); j++ ) {
                Attr attr = (Attr) attrList.item( j );
                if ( attr.getName().equals( ATTR_EXT_NAME ))
                    ext.setName( attr.getValue() );
                else if ( attr.getName().equals( ATTR_EXT_TARGET ))
                    ext.setTarget_name( attr.getValue() );
                else if ( attr.getName().equals( ATTR_EXT_URL ))
                    ext.setStart_url( attr.getValue() );
                else if ( attr.getName().equals( ATTR_EXT_DESC ))
                    ext.setDescription( attr.getValue() );
            }

            ext_list.add( ext );
        }
        return ext_list;
    }
    
    /** Utility methods finds the manifest tag, reads all it's attributes and
     * creates the manifest */
    private Manifest manifestFromXML() {
        if ( node.getNodeType() != Node.ELEMENT_NODE ||
                !( node instanceof Element ) ) {
            return null;
        }

        NodeList nodeList = ((Element)node).getElementsByTagName( "manifest" ); // NOI18N

        for( int i = 0; i < nodeList.getLength(); i++ ) {

            if ( nodeList.item( i ).getNodeType() != Node.ELEMENT_NODE ||
                    !( nodeList.item( i ) instanceof Element ) ) {
                break;
            }

            // ((Element)nodeList.item( i )).normalize();
            NamedNodeMap attrList = nodeList.item( i ).getAttributes();
            Manifest mf = new Manifest();
            Attributes mfAttrs = mf.getMainAttributes();


            for( int j = 0; j < attrList.getLength(); j++ ) {
                Attr attr = (Attr) attrList.item( j );
                mfAttrs.put( new Attributes.Name( attr.getName() ), attr.getValue() );
            }

            return mf;
        }
        return null;
    }

    /** Gets the licence from XML file
    */
    private String licenseFromXML( String name, Element docElement ) {
        NodeList nodeList = docElement.getElementsByTagName( "license" ); // NOI18N

        for( int i = 0; i < nodeList.getLength(); i++ ) {

            Node nameAttr = nodeList.item(i).getAttributes().getNamedItem( "name" ); // NOI18N

            if ( nameAttr == null )
                continue;

            if ( nameAttr.getNodeValue().equals( name ) ) { // licence found
                StringBuffer sb = new StringBuffer();

                // ((Element)nodeList.item( i )).normalize();
                NodeList innerList = nodeList.item( i ).getChildNodes();

                for( int j = 0; j < innerList.getLength(); j++ ) {
		    short type = innerList.item( j ).getNodeType();
                    if ( type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE )  {
                        sb.append( innerList.item( j ).getNodeValue() );
                    }
                }
                return sb.toString();
            }
        }

        // licence not found
        if (name != null) {
            ErrorManager.getDefault().log("[AutoUpdate] warning: no license found with name " + name);
        }
        return null;
    }

    /** Getter for property external.
     * @return Value of property external.
     */
    List getExternals() {
        return externals;
    }
    
    /** Returns true if update must be installed into install dir.
     * @return true if must be install globaly.
     */
    public boolean isForcedGlobal () {
        return depending || ! isAbleToInstallToUserDir ();
    }
    
    /** Sets the update is depending on install dir. Must be installed into shared dir.
     * @param depending New value of property depending.
     */
    public void setDepending(boolean depending) {
        this.depending = depending;        
    }
    
    /** Getter for property toInstallDir.
     * @return Value of property toInstallDir.
     */
    public boolean isToInstallDir() {
        if (isForcedGlobal ()) {
            return true;
        } else {
            
            // the global attribute is stronger then toInstallDir
            if (isGlobal () != null) {
                return isGlobal ().booleanValue ();
            } else {
                return this.toInstallDir;
            }
            
        }
    }
    
    /** Setter for property toInstallDir.
     * @param toInstallDir New value of property toInstallDir.
     */
    public void setToInstallDir(boolean toInstallDir) throws IllegalArgumentException {
        if (isGlobal () != null && toInstallDir != isGlobal ().booleanValue ()) {
            throw new IllegalArgumentException ("Cannot setToInstallDir (" + toInstallDir + ") when isGlobal is set to " + isGlobal ()); // NOI18N
        } else if (isForcedGlobal () && ! toInstallDir) {
            throw new IllegalArgumentException ("Cannot setToInstallDir (" + toInstallDir + ") when isForderGlobal is " + isForcedGlobal ()); // NOI18N
        }
        
        this.toInstallDir = toInstallDir;
    }
    
    /** Getter for property downloadStarted.
     * @return Value of property downloadStarted.
     */
    public boolean isDownloadStarted() {
        return this.downloadStarted;
    }
    
    /** Setter for property downloadStarted.
     * @param downloadStarted New value of property downloadStarted.
     */
    public void setDownloadStarted(boolean downloadStarted) {
        this.downloadStarted = downloadStarted;
    }
    
    /** Getter for property safeToInstall.
     * @return Value of property safeToInstall.
     */
    boolean isSafeToInstall() {
        return safeToInstall && isNew() && !isForcedGlobal () ;
    }
    
    /** Setter for property safeToInstall.
     * @param safeToInstall New value of property safeToInstall.
     */
    void setSafeToInstall(boolean safeToInstall) {
        this.safeToInstall = safeToInstall;
    }
    
    /** Getter for attribute isGlobal read from NBM/Info.xml.
     * @return Value of property isGlobal or null if not  set.
     */
    Boolean isGlobal () {
        return isGlobal;
    }
    
    /** Getter for property jarList.
     * @return Value of property jarList.
     */
    List getJarList() {
        return this.jarList;
    }
    
    /** Setter for property jarList.
     * @param jarList New value of property jarList.
     */
    void setJarList(List jarList) {
        this.jarList = jarList;
    }
    
    void addToJarList(String jarName) {
        jarList.add( jarName );
    }
    
    /** Searches the shared installations (each cluster in sequence)
     * directories and chooses the one that this module should be installed 
     * into.
     * <P>
     * This is done according to the rules described at
     * http://openide.netbeans.org/proposals/arch/installation.html#influence-on-autoupdate
     *
     * @return the directory we want to install at or <code>null</code> if the
     *   module cannot be installed into shared location
     */
    final File findInstallDirectory () {
        File firstInstallable = null;
        
        Iterator en = org.netbeans.updater.UpdateTracking.clusters (false).iterator ();
        while (en.hasNext ()) {
            File f = (File)en.next ();
            org.netbeans.updater.UpdateTracking ut;
            ut = org.netbeans.updater.UpdateTracking.getTracking (f, false);
            if (ut != null && firstInstallable == null) {
                firstInstallable = f;
            }
    
            if (ut != null && ut.isModuleInstalled (this.getInfoCodenamebase ())) {
                // if module is installed here, let's stop the 
                // search and treat this as the install dir
                return f;
            }
        }
        return firstInstallable;
    }
    
    /** Returns true if a module can be installed into user directory.
     * We cannot install into user dir if our files would clash with some
     * files already installed in some global cluster.
     */
    final boolean isAbleToInstallToUserDir () {
        HashSet files = new HashSet ();
        if (nbmFile == null) {
            files.addAll (jarList);
        } else {
            try {
                JarFile f = new JarFile (nbmFile);
                Enumeration en = f.entries ();
                while (en.hasMoreElements ()) {
                    JarEntry e = (JarEntry)en.nextElement ();
                    String s = e.getName ();
                    if (s.startsWith ("netbeans/")) {
                        s = s.substring (9);
                    }
                    files.add (s);
                }
            } catch (IOException ex) {
                assert false : "IOException caught: " + ex.getMessage ();
                return false;
            }
        }        
        
        Iterator en = org.netbeans.updater.UpdateTracking.clusters (false).iterator ();
        while (en.hasNext ()) {
            File f = (File)en.next ();
            
            Iterator it = files.iterator ();
            while (it.hasNext ()) {
                String entry = (String)it.next ();
                if (entry.startsWith (NBM_CORE) || entry.startsWith (NBM_LIB)) {
                    File my = new File (f, entry);
                    if (! my.isDirectory () && my.exists ()) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    /* This innerclass represents licence cache
    */
    /*
    private static class LicenceCache extends Object {
      
      private HashMap map;
      
      
      LicenceCache() {
        map = new HashMap();
      }
      
      String getLicence ( URL url ) {      
        Set keys = map.keySet();
        Iterator it = keys.iterator();
        while( it.hasNext() ) {
          URL keyUrl = (URL)it.next();
          if ( keyUrl.equals( url ) ) {
            return (String)map.get( keyUrl );
          }
        }
        
        return null;
      }
      
      void addLicence ( URL url, String text ) {
        map.put( url, text );
      }
}
    */
    static class ErrorCatcher implements org.xml.sax.ErrorHandler {
        private void message (String level, org.xml.sax.SAXParseException e) {
        }

        public void error (org.xml.sax.SAXParseException e) {
        }

        public void warning (org.xml.sax.SAXParseException e) {
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);            
        }

        public void fatalError (org.xml.sax.SAXParseException e) {
        }
    } //end of inner class ErrorPrinter

    class External extends Object {
        
        /** Holds value of property name. */
        private String name;
        
        /** Holds value of property target_name. */
        private String target_name;
        
        /** Holds value of property start_url. */
        private String start_url;
        
        /** Holds value of property description. */
        private String description;
        
        /** Getter for property name.
         * @return Value of property name.
         */
        public String getName() {
            return name;
        }
        
        /** Setter for property name.
         * @param name New value of property name.
         */
        public void setName(String name) {
            this.name = name;
        }
        
        /** Getter for property target_name.
         * @return Value of property target_name.
         */
        public String getTarget_name() {
            return target_name;
        }
        
        /** Setter for property target_name.
         * @param target_name New value of property target_name.
         */
        public void setTarget_name(String target_name) {
            this.target_name = target_name;
        }
        
        /** Getter for property start_url.
         * @return Value of property start_url.
         */
        public String getStart_url() {
            return start_url;
        }
        
        /** Setter for property start_url.
         * @param start_url New value of property start_url.
         */
        public void setStart_url(String start_url) {
            this.start_url = start_url;
        }
        
        /** Getter for property description.
         * @return Value of property description.
         */
        public String getDescription() {
            return description;
        }
        
        /** Setter for property description.
         * @param description New value of property description.
         */
        public void setDescription(String description) {
            this.description = description;
        }
        
    }
}
