/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.web.project;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.util.*;
import org.netbeans.api.j2ee.core.Profile;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.modules.j2ee.dd.api.web.WebApp;
import org.netbeans.modules.j2ee.dd.api.web.WebAppMetadata;
import org.netbeans.modules.j2ee.dd.api.webservices.WebservicesMetadata;
import org.netbeans.modules.j2ee.deployment.common.api.ConfigurationException;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeModuleFactory;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeModuleProvider;
import org.netbeans.modules.j2ee.deployment.common.api.EjbChangeDescriptor;
import org.netbeans.modules.web.project.classpath.ClassPathProviderImpl;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.openide.filesystems.FileObject;
import org.netbeans.modules.j2ee.deployment.devmodules.api.*;
import org.netbeans.modules.web.project.ui.customizer.WebProjectProperties;
import org.netbeans.spi.java.classpath.ClassPathProvider;
import org.openide.filesystems.FileUtil;
import org.openide.NotifyDescriptor;
import org.openide.DialogDisplayer;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.netbeans.modules.j2ee.dd.spi.MetadataUnit;
import org.netbeans.modules.j2ee.dd.spi.web.WebAppMetadataModelFactory;
import org.netbeans.modules.j2ee.dd.spi.webservices.WebservicesMetadataModelFactory;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeModuleImplementation2;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.ResourceChangeReporterFactory;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.ResourceChangeReporterImplementation;
import org.netbeans.modules.j2ee.metadata.model.api.MetadataModel;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.web.spi.webmodule.WebModuleImplementation2;
import org.netbeans.modules.websvc.spi.webservices.WebServicesConstants;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileEvent;

/** A web module implementation on top of project.
 *
 * @author  Pavel Buzek
 * @author ads
 */
public final class ProjectWebModule extends J2eeModuleProvider
        implements J2eeModuleImplementation2, ModuleChangeReporter,
        EjbChangeDescriptor, PropertyChangeListener,
        Lookup.Provider
{

    public static final String FOLDER_WEB_INF = "WEB-INF";//NOI18N
//    public static final String FOLDER_CLASSES = "classes";//NOI18N
//    public static final String FOLDER_LIB     = "lib";//NOI18N
    public static final String FILE_DD        = "web.xml";//NOI18N

    public static final String LOOKUP_ITEM    = "lookup.item";//NOI18N

    private final ResourceChangeReporter rcr = ResourceChangeReporterFactory.createResourceChangeReporter(new WebResourceChangeReporter());

    private WebProject project;
    private UpdateHelper helper;
    private ClassPathProviderImpl cpProvider;
    private String fakeServerInstId = null; // used to get access to properties of other servers
    private Lookup myLookup;
    private InstanceContent myContent;

    private long notificationTimeout = 0; // used to suppress repeating the same messages

    private MetadataModel<WebAppMetadata> webAppMetadataModel;
    private MetadataModel<WebAppMetadata> webAppAnnMetadataModel;
    private MetadataModel<WebservicesMetadata> webservicesMetadataModel;

    private PropertyChangeSupport propertyChangeSupport;

    private J2eeModule j2eeModule;

    ProjectWebModule (WebProject project, UpdateHelper helper, ClassPathProviderImpl cpProvider) {
        this.project = project;
        this.helper = helper;
        this.cpProvider = cpProvider;
        myContent = new InstanceContent();
        myLookup = new AbstractLookup( myContent );
        //project.evaluator().addPropertyChangeListener(this);
    }

    public Lookup getLookup(){
        return myLookup;
    }

    public void addCookie( Object cookie ){
        if ( cookie == null ){
            return;
        }
        Object old = getLookup().lookup(cookie.getClass());
        myContent.add( cookie );
        getPropertyChangeSupport().firePropertyChange(LOOKUP_ITEM, old, cookie);
    }

    public void removeCookie( Object cookie ){
        if ( cookie == null ){
            return;
        }
        myContent.remove( cookie);
        getPropertyChangeSupport().firePropertyChange(LOOKUP_ITEM, cookie, null);
    }

    public FileObject getDeploymentDescriptor() {
        return getDeploymentDescriptor(false);
    }

    public FileObject getDeploymentDescriptor(boolean silent) {
        FileObject webInfFo = getWebInf(silent);
        if (webInfFo==null) {
            return null;
        }
        FileObject dd = webInfFo.getFileObject (FILE_DD);
        if (dd == null && !silent
                && (Profile.J2EE_13.equals(getJ2eeProfile()) ||
                    Profile.J2EE_14.equals(getJ2eeProfile()))) {
            showErrorMessage(NbBundle.getMessage(ProjectWebModule.class,"MSG_WebXmlNotFound", //NOI18N
                    webInfFo.getPath()));
        }
        return dd;
    }

    public String getContextPath () {
        try {
            return getConfigSupport().getWebContextRoot();
        } catch (ConfigurationException e) {
            Exceptions.printStackTrace(e);
            return null;
        }
    }

    public void setContextPath (String path) {
        try {
            getConfigSupport().setWebContextRoot(path);
        } catch (ConfigurationException e) {
            Exceptions.printStackTrace(e);
        }
    }

    public String getContextPath (String serverInstId) {
        fakeServerInstId = serverInstId;
        String result = getContextPath();
        fakeServerInstId = null;
        return result;
    }

    public void setContextPath (String serverInstId, String path) {
        fakeServerInstId = serverInstId;
        setContextPath(path);
        fakeServerInstId = null;
    }

    private void showErrorMessage(final String message) {
        synchronized (this) {
            if(new Date().getTime() > notificationTimeout && isProjectOpened()) {
                // set timeout to suppress the same messages during next 20 seconds (feel free to adjust the timeout
                // using more suitable value)
                notificationTimeout = new Date().getTime() + 20000;
            } else {
                return;
            }
        }
        // #240818
        DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message(message, NotifyDescriptor.ERROR_MESSAGE));
    }

    public FileObject getDocumentBase () {
        return getDocumentBase(false);
    }

    public FileObject getDocumentBase (boolean silent) {
        String value = helper.getAntProjectHelper().getStandardPropertyEvaluator()
                .getProperty(WebProjectProperties.WEB_DOCBASE_DIR);

        return resolveDocumentBase(value, silent);
    }

    FileObject resolveDocumentBase(String value, boolean silent) {
        FileObject docBase = value != null ? helper.getAntProjectHelper().resolveFileObject(value) : null;
        if (docBase == null && !silent) {
            String path = (value != null ? helper.getAntProjectHelper().resolvePath(value) : null);
            String errorMessage;
            if (path != null) {
                errorMessage = NbBundle.getMessage(ProjectWebModule.class, "MSG_DocBase_Corrupted", project.getName(), path);
            } else {
                errorMessage = NbBundle.getMessage(ProjectWebModule.class, "MSG_DocBase_Corrupted_Unknown", project.getName());
            }
            showErrorMessage(errorMessage);
        }
        return docBase;
    }

    @Deprecated
    public FileObject[] getJavaSources() {
        return project.getSourceRoots().getRoots();
    }

//    public ClassPath getJavaSources () {
//        ClassPathProvider cpp = (ClassPathProvider) project.getLookup ().lookup (ClassPathProvider.class);
//        if (cpp != null) {
//            return cpp.findClassPath (getFileObject ("src.dir"), ClassPath.SOURCE); //NOI18N
//        }
//        return null;
//    }

    public FileObject getWebInf () {
        return getWebInf(false);
    }

    public FileObject getWebInf (boolean silent) {
        String value = helper.getAntProjectHelper().getStandardPropertyEvaluator()
                .getProperty(WebProjectProperties.WEBINF_DIR);

        return resolveWebInf(null, value, silent, false);
    }

    FileObject resolveWebInf(String docBaseValue, String webInfValue, boolean silent, boolean useDocBase) {
        FileObject webInf = null;
        if (webInfValue != null){
             webInf = helper.getAntProjectHelper().resolveFileObject(webInfValue);
//             if (webInf == null && forceCreate){
//                webInf = helper.getAntProjectHelper().getProjectDirectory();
//                StringTokenizer st = new StringTokenizer(webInfValue, "/");
//                while (st.hasMoreTokens()) {
//                    String nameExt = st.nextToken();
//                    try {
//                        FileObject tmp = webInf.getFileObject(nameExt, null);
//                        webInf = tmp != null ? tmp : webInf.createFolder(nameExt);
//                    } catch (IOException ex) {
//                        Exceptions.printStackTrace(ex);
//                        webInf = null;
//                        break;
//                    }
//                }
//             }
        }

        //temporary solution for < 6.0 projects
        if (webInf == null) {
            FileObject documentBase = null;
            if (useDocBase) {
                documentBase = resolveDocumentBase(docBaseValue, silent);
            } else {
                documentBase = getDocumentBase(silent);
            }
            if (documentBase == null) {
                return null;
            }
            webInf = documentBase.getFileObject (FOLDER_WEB_INF);
//            if (webInf == null && forceCreate){
//                try {
//                    webInf = documentBase.createFolder(FOLDER_WEB_INF);
//                } catch (IOException ex) {
//                    Exceptions.printStackTrace(ex);
//                }
//            }
        }

        if (webInf == null && !silent) {
            showErrorMessage(NbBundle.getMessage(ProjectWebModule.class,"MSG_WebInfCorrupted2")); //NOI18N
        }
        return webInf;
    }

    public FileObject getConfDir() {
        return getFileObject(WebProjectProperties.CONF_DIR);
    }

    public File getConfDirAsFile() {
        return getFile(WebProjectProperties.CONF_DIR);
    }

    public FileObject getPersistenceXmlDir() {
        return getFileObject(WebProjectProperties.PERSISTENCE_XML_DIR);
    }

    public File getPersistenceXmlDirAsFile() {
        return getFile(WebProjectProperties.PERSISTENCE_XML_DIR);
    }

    public ClassPathProvider getClassPathProvider () {
        return project.getLookup().lookup(ClassPathProvider.class);
    }

    public FileObject getArchive () {
        return getFileObject ("dist.war"); //NOI18N
    }

    private FileObject getFileObject(String propname) {
        String prop = helper.getAntProjectHelper().getStandardPropertyEvaluator().getProperty(propname);
        if (prop != null) {
            return helper.getAntProjectHelper().resolveFileObject(prop);
        } else {
            return null;
        }
    }

    private File getFile(String propname) {
        String prop = helper.getAntProjectHelper().getStandardPropertyEvaluator().getProperty(propname);
        if (prop != null) {
            return helper.getAntProjectHelper().resolveFile(prop);
        } else {
            return null;
        }
    }

    public synchronized J2eeModule getJ2eeModule () {
        if (j2eeModule == null) {
            j2eeModule = J2eeModuleFactory.createJ2eeModule(this);
        }
        return j2eeModule;
    }

    public org.netbeans.modules.j2ee.deployment.devmodules.api.ModuleChangeReporter getModuleChangeReporter () {
        return this;
    }

    @Override
    public ResourceChangeReporter getResourceChangeReporter() {
        return rcr;
    }

    @Override
    public DeployOnSaveSupport getDeployOnSaveSupport() {
        return project.getDeployOnSaveSupport();
    }

    @Override
    public boolean isOnlyCompileOnSaveEnabled() {
        return Boolean.parseBoolean(project.evaluator().getProperty(WebProjectProperties.J2EE_COMPILE_ON_SAVE)) &&
            !Boolean.parseBoolean(project.evaluator().getProperty(WebProjectProperties.J2EE_DEPLOY_ON_SAVE));
    }


    public File getDeploymentConfigurationFile(String name) {
        assert name != null : "File name of the deployement configuration file can't be null"; //NOI18N

        String path = getConfigSupport().getContentRelativePath(name);
        if (path == null) {
            path = name;
        }

        if (path.startsWith("WEB-INF/")) { //NOI18N
            path = path.substring(8); //removing "WEB-INF/"

            FileObject webInf = getWebInf();
            if (webInf == null) {
                //in case that docbase is null ... but normally it should not be
                return new File(getConfDirAsFile(), name);
            }
            return new File(FileUtil.toFile(webInf), path);
        } else {
            FileObject documentBase = getDocumentBase();
            if (documentBase == null) {
                //in case that docbase is null ... but normally it should not be
                return new File(getConfDirAsFile(), name);
            }
            return new File(FileUtil.toFile(documentBase), path);
        }
    }

    public FileObject getModuleFolder () {
        return getDocumentBase ();
    }

    public String getServerID () {
        String inst = getServerInstanceID ();
        if (inst != null) {
            String id = Deployment.getDefault().getServerID(inst);
            if (id != null) {
                return id;
            }
        }
        return helper.getAntProjectHelper().getStandardPropertyEvaluator ().getProperty (WebProjectProperties.J2EE_SERVER_TYPE);
    }

    public String getServerInstanceID () {
        if (fakeServerInstId != null)
            return fakeServerInstId;
        return helper.getAntProjectHelper().getStandardPropertyEvaluator ().getProperty (WebProjectProperties.J2EE_SERVER_INSTANCE);
    }

    public void setServerInstanceID(String severInstanceID) {
        WebProjectProperties.setServerInstance(project, helper, severInstanceID);
    }

    public Iterator<J2eeModule.RootedEntry> getArchiveContents () throws java.io.IOException {
        FileObject content = getContentDirectory();
        content.refresh();
        return new IT(content);
    }

    public FileObject getContentDirectory() {
        return getFileObject ("build.web.dir"); //NOI18N
    }

    public FileObject getBuildDirectory() {
        return getFileObject ("build.dir"); //NOI18N
    }

    public File getContentDirectoryAsFile() {
        return getFile ("build.web.dir"); //NOI18N
    }

    public <T> MetadataModel<T> getMetadataModel(Class<T> type) {
        if (type == WebAppMetadata.class) {
            @SuppressWarnings("unchecked") // NOI18N
            MetadataModel<T> model = (MetadataModel<T>)getAnnotationMetadataModel();
            return model;
        } else if (type == WebservicesMetadata.class) {
            @SuppressWarnings("unchecked") // NOI18N
            MetadataModel<T> model = (MetadataModel<T>)getWebservicesMetadataModel();
            return model;
        }
        return null;
    }

    public synchronized MetadataModel<WebAppMetadata> getMetadataModel() {
        if (webAppMetadataModel == null) {
            FileObject ddFO = getDeploymentDescriptor();
            final FileObject webInf = getWebInf(true);
            if (ddFO == null && webInf != null) {
                webInf.addFileChangeListener(new FileChangeAdapter() {
                    @Override
                    public void fileDataCreated(FileEvent fe) {
                        if (FILE_DD.equals(fe.getFile().getNameExt())) {
                            webInf.removeFileChangeListener(this);
                            resetMetadataModel();
                        }
                    }
                });
            }
            File ddFile = ddFO != null ? FileUtil.toFile(ddFO) : null;
            MetadataUnit metadataUnit = MetadataUnit.create(
                cpProvider.getProjectSourcesClassPath(ClassPath.BOOT),
                cpProvider.getProjectSourcesClassPath(ClassPath.COMPILE),
                cpProvider.getProjectSourcesClassPath(ClassPath.SOURCE),
                ddFile);
            webAppMetadataModel = WebAppMetadataModelFactory.createMetadataModel(metadataUnit, true);
        }
        return webAppMetadataModel;
    }

    private synchronized void resetMetadataModel() {
        webAppMetadataModel = null;
    }

    /**
     * The server plugin needs all models to be either merged on annotation-based.
     * Currently only the web model does a bit of merging, other models don't. So
     * for web we actually need two models (one for the server plugins and another
     * for everyone else). Temporary solution until merging is implemented
     * in all models.
     */
    public synchronized MetadataModel<WebAppMetadata> getAnnotationMetadataModel() {
        if (webAppAnnMetadataModel == null) {
            FileObject ddFO = getDeploymentDescriptor();
            File ddFile = ddFO != null ? FileUtil.toFile(ddFO) : null;
            MetadataUnit metadataUnit = MetadataUnit.create(
                cpProvider.getProjectSourcesClassPath(ClassPath.BOOT),
                cpProvider.getProjectSourcesClassPath(ClassPath.COMPILE),
                cpProvider.getProjectSourcesClassPath(ClassPath.SOURCE),
                // XXX: add listening on deplymentDescriptor
                ddFile);
            webAppAnnMetadataModel = WebAppMetadataModelFactory.createMetadataModel(metadataUnit, false);
        }
        return webAppAnnMetadataModel;
    }

    public synchronized MetadataModel<WebservicesMetadata> getWebservicesMetadataModel() {
        if (webservicesMetadataModel == null) {
            FileObject ddFO = getDD();
            File ddFile = ddFO != null ? FileUtil.toFile(ddFO) : null;
            MetadataUnit metadataUnit = MetadataUnit.create(
                cpProvider.getProjectSourcesClassPath(ClassPath.BOOT),
                cpProvider.getProjectSourcesClassPath(ClassPath.COMPILE),
                cpProvider.getProjectSourcesClassPath(ClassPath.SOURCE),
                // XXX: add listening on deplymentDescriptor
                ddFile);
            webservicesMetadataModel = WebservicesMetadataModelFactory.createMetadataModel(metadataUnit);
        }
        return webservicesMetadataModel;
    }

    public void uncacheDescriptors() {
        // this.getConfigSupport().resetStorage();
        // reset timeout when closing the project
        synchronized (this) {
            notificationTimeout = 0;
        }
    }

    // TODO MetadataModel: rewrite when MetadataModel is ready
//    private Webservices getWebservices() {
//        if (Util.isJavaEE5orHigher(project)) {
//            WebServicesSupport wss = WebServicesSupport.getWebServicesSupport(project.getProjectDirectory());
//            try {
//                return org.netbeans.modules.j2ee.dd.api.webservices.DDProvider.getDefault().getMergedDDRoot(wss);
//            } catch (IOException ex) {
//                ErrorManager.getDefault().notify(ex);
//            }
//        } else {
//            FileObject wsdd = getDD();
//            if(wsdd != null) {
//                try {
//                    return org.netbeans.modules.j2ee.dd.api.webservices.DDProvider.getDefault()
//                    .getDDRoot(getDD());
//                } catch (java.io.IOException e) {
//                    org.openide.ErrorManager.getDefault().log(e.getLocalizedMessage());
//                }
//            }
//        }
//        return null;
//    }

    public org.netbeans.modules.j2ee.deployment.common.api.EjbChangeDescriptor getEjbChanges (long timestamp) {
        return this;
    }

    public J2eeModule.Type getModuleType () {
        return J2eeModule.Type.WAR;
    }

    @Override
    public String getModuleVersion () {
        // return a version based on the Java EE version
        Profile platformVersion = getJ2eeProfile();
        if (Profile.JAKARTA_EE_10_FULL.equals(platformVersion) || Profile.JAKARTA_EE_10_WEB.equals(platformVersion)) {
            return WebApp.VERSION_6_0;
        } else if (Profile.JAKARTA_EE_9_1_FULL.equals(platformVersion) || Profile.JAKARTA_EE_9_1_WEB.equals(platformVersion)
                || Profile.JAKARTA_EE_9_FULL.equals(platformVersion) || Profile.JAKARTA_EE_9_WEB.equals(platformVersion)) {
            return WebApp.VERSION_5_0;
        } else if (Profile.JAKARTA_EE_8_FULL.equals(platformVersion) || Profile.JAKARTA_EE_8_WEB.equals(platformVersion) 
                || Profile.JAVA_EE_8_FULL.equals(platformVersion) || Profile.JAVA_EE_8_WEB.equals(platformVersion)) {
            return WebApp.VERSION_4_0;
        } else if (Profile.JAVA_EE_7_FULL.equals(platformVersion) || Profile.JAVA_EE_7_WEB.equals(platformVersion)) {
            return WebApp.VERSION_3_1;
        } else if (Profile.JAVA_EE_6_FULL.equals(platformVersion) || Profile.JAVA_EE_6_WEB.equals(platformVersion)) {
            return WebApp.VERSION_3_0;
        } else if (Profile.JAVA_EE_5.equals(platformVersion)) {
            return WebApp.VERSION_2_5;
        } else if (Profile.J2EE_14.equals(platformVersion)) {
            return WebApp.VERSION_2_4;
        } else {
            // return 3.1 as default value
            return WebApp.VERSION_3_1;
        }
    }

    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(org.netbeans.modules.j2ee.dd.api.web.WebApp.PROPERTY_VERSION)) {
            String oldVersion = (String) evt.getOldValue();
            String newVersion = (String) evt.getNewValue();
            getPropertyChangeSupport().firePropertyChange(J2eeModule.PROP_MODULE_VERSION, oldVersion, newVersion);
        } else if (evt.getPropertyName ().equals (WebProjectProperties.J2EE_SERVER_INSTANCE)) {
            Deployment d = Deployment.getDefault ();
            String oldServerID = evt.getOldValue () == null ? null : d.getServerID ((String) evt.getOldValue ());
            String newServerID = evt.getNewValue () == null ? null : d.getServerID ((String) evt.getNewValue ());
            fireServerChange (oldServerID, newServerID);
        }  else if (WebProjectProperties.RESOURCE_DIR.equals(evt.getPropertyName())) {
            String oldValue = (String) evt.getOldValue();
            String newValue = (String) evt.getNewValue();
            getPropertyChangeSupport().firePropertyChange(
                    J2eeModule.PROP_RESOURCE_DIRECTORY,
                    oldValue == null ? null : new File(oldValue),
                    newValue == null ? null : new File(newValue));
        }  else if (WebProjectProperties.WEB_DOCBASE_DIR.equals(evt.getPropertyName())) {
            getPropertyChangeSupport().firePropertyChange(
                    WebModuleImplementation2.PROPERTY_DOCUMENT_BASE,
                    (String)evt.getOldValue(),
                    (String)evt.getNewValue());
        }  else if (WebProjectProperties.WEBINF_DIR.equals(evt.getPropertyName())) {
            getPropertyChangeSupport().firePropertyChange(
                    WebModuleImplementation2.PROPERTY_WEB_INF,
                    (String)evt.getOldValue(),
                    (String)evt.getNewValue());
        }
    }

    public String getUrl () {
         EditableProperties ep =  helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
         String warName = ep.getProperty(WebProjectProperties.WAR_NAME);
         return warName == null ? "" : ("/"+warName); //NOI18N
    }

    public boolean isManifestChanged (long timestamp) {
        return false;
    }

    public void setUrl (String url) {
        throw new UnsupportedOperationException ("Cannot customize URL of web module"); //NOI18N
    }

    public boolean ejbsChanged () {
        return false;
    }

    public String[] getChangedEjbs () {
        return new String[] {};
    }

    public Profile getJ2eeProfile () {
        return Profile.fromPropertiesString(helper.getAntProjectHelper().getStandardPropertyEvaluator ().getProperty(WebProjectProperties.J2EE_PLATFORM));
    }

    public FileObject getDD() {
       FileObject webInfFo = getWebInf();
       if (webInfFo==null) {
           showErrorMessage(NbBundle.getMessage(ProjectWebModule.class,"MSG_WebInfCorrupted"));
           return null;
       }
       return getWebInf().getFileObject(WebServicesConstants.WEBSERVICES_DD, "xml"); // NOI18N
   }

    @Override
    public FileObject[] getSourceRoots() {
        Sources sources = ProjectUtils.getSources(project);
        SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);

        List<FileObject> roots = new LinkedList<FileObject>();
        FileObject documentBase = getDocumentBase();
        if (documentBase != null)
            roots.add(documentBase);

        for (int i = 0; i < groups.length; i++) {
            roots.add(groups[i].getRootFolder());
        }

        FileObject[] rootArray = new FileObject[roots.size()];
        return roots.toArray(rootArray);
    }

    private boolean isProjectOpened() {
        // XXX workaround: OpenProjects.getDefault() can be null
        // when called from ProjectOpenedHook.projectOpened() upon IDE startup
        if (OpenProjects.getDefault() == null)
            return true;

        Project[] projects = OpenProjects.getDefault().getOpenProjects();
        for (int i = 0; i < projects.length; i++) {
            if (projects[i].equals(project))
                return true;
        }
        return false;
    }

    @Override
    public File getResourceDirectory() {
        File f = getFile(WebProjectProperties.RESOURCE_DIR);
        if (f == null) {
            f = new File(FileUtil.toFile(project.getProjectDirectory()), "setup"); // NOI18N
        }
        return f;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        //synchronized (this) {
            // XXX need to listen on the module version
            // if (!webAppPropChangeLInitialized) {
            //     try {
            //         project.getWebModule().getMetadataModel().runReadAction(new MetadataModelAction<WebAppMetadata, Void>() {
            //             public Void run(WebAppMetadata metadata) throws MetadataModelException, IOException {
            //                 WebApp webAp p = metadata.getRoot();
            //                 PropertyChangeListener l = (PropertyChangeListener) WeakListeners.create(PropertyChangeListener.class, ProjectWebModule.this, webApp);
            //                 webApp.addPropertyChangeListener(l);
            //                 return null;
            //             }
            //         });
            //         webAppPropChangeLInitialized = true;
            //     } catch (MetadataModelException e) {
            //         // TODO MetadataModel: how should we handle this?
            //     } catch (IOException e) {
            //         // TODO MetadataModel: how should we handle this?
            //     }
            // }
        //}
        getPropertyChangeSupport().addPropertyChangeListener(listener);
    }

    public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
        if (propertyChangeSupport == null) {
            return;
        }
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    private synchronized PropertyChangeSupport getPropertyChangeSupport() {
        if (propertyChangeSupport == null) {
            propertyChangeSupport = new PropertyChangeSupport(this);
        }
        return propertyChangeSupport;
    }

    private class WebResourceChangeReporter implements ResourceChangeReporterImplementation {

        public boolean isServerResourceChanged(long lastDeploy) {
            File resDir = getResourceDirectory();
            if (resDir != null && resDir.exists() && resDir.isDirectory()) {
                File[] children = resDir.listFiles();
                if (children != null) {
                    for (File file : children) {
                        if (file.lastModified() > lastDeploy) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    }

    private static class IT implements Iterator<J2eeModule.RootedEntry> {
        ArrayList<FileObject> ch;
        FileObject root;

        private IT (FileObject f) {
            this.ch = new ArrayList<FileObject>();
            ch.add (f);
            this.root = f;
        }

        public boolean hasNext () {
            return ! ch.isEmpty();
        }

        public J2eeModule.RootedEntry next () {
            FileObject f = ch.get(0);
            ch.remove(0);
            if (f.isFolder()) {
                f.refresh();
                FileObject chArr[] = f.getChildren ();
                for (int i = 0; i < chArr.length; i++) {
                    ch.add(chArr [i]);
                }
            }
            return new FSRootRE (root, f);
        }

        public void remove () {
            throw new UnsupportedOperationException ();
        }

    }

    private static final class FSRootRE implements J2eeModule.RootedEntry {
        FileObject f;
        FileObject root;

        FSRootRE (FileObject root, FileObject f) {
            this.f = f;
            this.root = root;
        }

        public FileObject getFileObject () {
            return f;
        }

        public String getRelativePath () {
            return FileUtil.getRelativePath (root, f);
        }
    }
}
