/*
 * 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.io.*;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.swing.Icon;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ant.AntBuildExtender;
import org.netbeans.api.queries.FileBuiltQuery.Status;
import org.netbeans.api.j2ee.core.Profile;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.project.classpath.ProjectClassPathModifier;
import org.netbeans.modules.j2ee.dd.api.ejb.EjbJarMetadata;
import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelAction;
import org.netbeans.modules.java.api.common.Roots;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.ArtifactListener.Artifact;
import org.netbeans.modules.web.common.spi.ProjectWebRootProvider;
import org.netbeans.modules.web.project.api.WebPropertyEvaluator;
import org.netbeans.modules.web.project.jaxws.WebProjectJAXWSClientSupport;
import org.netbeans.modules.web.project.jaxws.WebProjectJAXWSSupport;
import org.netbeans.modules.websvc.api.jaxws.client.JAXWSClientSupport;
import org.netbeans.modules.websvc.jaxws.api.JAXWSSupport;
import org.netbeans.modules.websvc.jaxws.spi.JAXWSSupportFactory;
import org.netbeans.modules.websvc.spi.client.WebServicesClientSupportFactory;
import org.netbeans.modules.websvc.spi.jaxws.client.JAXWSClientSupportFactory;
import org.netbeans.spi.queries.FileBuiltQueryImplementation;
import org.openide.util.ChangeSupport;
import org.openide.util.ImageUtilities;
import org.openide.util.RequestProcessor.Task;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.lookup.Lookups;
import org.netbeans.modules.web.api.webmodule.WebModule;
import org.netbeans.modules.web.spi.webmodule.WebModuleFactory;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ant.AntArtifact;
import org.netbeans.modules.web.project.classpath.ClassPathProviderImpl;
import org.netbeans.modules.web.project.ui.WebLogicalViewProvider;
import org.netbeans.modules.web.project.ui.customizer.WebProjectProperties;
import org.netbeans.spi.project.AuxiliaryConfiguration;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.libraries.Library;
import org.netbeans.api.project.libraries.LibraryManager;
import org.netbeans.api.project.ui.ProjectProblems;
import org.netbeans.modules.j2ee.api.ejbjar.EjbJar;
import org.netbeans.modules.j2ee.common.ClasspathUtil;
import org.netbeans.modules.j2ee.common.J2eeProjectCapabilities;
import org.netbeans.modules.j2ee.common.SharabilityUtility;
import org.netbeans.modules.j2ee.common.dd.DDHelper;
import org.netbeans.modules.javaee.project.api.ant.ArtifactCopyOnSaveSupport;
import org.netbeans.modules.javaee.project.api.ClientSideDevelopmentSupport;
import org.netbeans.modules.j2ee.persistence.spi.entitymanagergenerator.EntityManagerGenerationStrategyResolverFactory;
import org.netbeans.modules.javaee.project.api.PersistenceProviderSupplierImpl;
import org.netbeans.modules.javaee.project.api.ant.AntProjectConstants;
import org.netbeans.modules.j2ee.common.ProjectUtil;
import org.netbeans.modules.j2ee.common.ServerUtil;
import org.netbeans.modules.javaee.project.api.WhiteListUpdater;
import org.netbeans.modules.java.api.common.classpath.ClassPathModifier;
import org.netbeans.modules.java.api.common.classpath.ClassPathModifierSupport;
import org.netbeans.modules.j2ee.deployment.devmodules.api.J2eePlatform;
import org.netbeans.modules.java.api.common.SourceRoots;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.java.api.common.queries.QuerySupport;
import org.netbeans.spi.project.ant.AntArtifactProvider;
import org.netbeans.spi.project.ant.AntBuildExtenderFactory;
import org.netbeans.spi.project.ant.AntBuildExtenderImplementation;
import org.netbeans.spi.project.support.LookupProviderSupport;
import org.netbeans.spi.project.support.ant.AntProjectEvent;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.AntProjectListener;
import org.netbeans.spi.project.support.ant.GeneratedFilesHelper;
import org.netbeans.spi.project.support.ant.ProjectXmlSavedHook;
import org.netbeans.spi.project.ui.PrivilegedTemplates;
import org.netbeans.spi.project.ui.RecommendedTemplates;
import org.netbeans.spi.project.ui.support.UILookupMergerSupport;
import org.netbeans.spi.project.support.ant.ReferenceHelper;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.netbeans.spi.project.ui.ProjectOpenedHook;
import org.netbeans.modules.java.api.common.classpath.ClassPathSupport;
import org.netbeans.modules.java.api.common.project.ui.ClassPathUiSupport;
import org.netbeans.modules.javaee.project.api.ant.DeployOnSaveUtils;
import org.netbeans.modules.javaee.project.api.ant.ui.J2EEProjectProperties;
import org.netbeans.modules.j2ee.common.ui.BrokenServerLibrarySupport;
import org.netbeans.modules.j2ee.common.ui.BrokenServerSupport;
import org.netbeans.modules.javaee.project.spi.JavaEEProjectSettingsImplementation;
import org.netbeans.modules.j2ee.dd.api.web.WebAppMetadata;
import org.netbeans.modules.j2ee.deployment.devmodules.api.Deployment;
import org.netbeans.modules.j2ee.deployment.devmodules.api.InstanceRemovedException;
import org.netbeans.modules.j2ee.deployment.devmodules.api.J2eeModule.Type;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.ArtifactListener;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeModuleProvider.ConfigSupport.DeployOnSaveListener;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeModuleProvider.DeployOnSaveSupport;
import org.netbeans.modules.j2ee.metadata.model.api.MetadataModel;
import org.netbeans.modules.j2ee.spi.ejbjar.EjbJarFactory;
import org.netbeans.modules.j2ee.spi.ejbjar.support.EjbJarSupport;
import org.netbeans.modules.java.api.common.project.ProjectProperties;
import org.netbeans.modules.javaee.project.api.JavaEEProjectSettingConstants;
import org.netbeans.modules.javaee.project.api.ant.AntProjectUtil;
import org.netbeans.modules.javaee.project.api.problems.PlatformUpdatedCallBackImpl;
import org.netbeans.modules.web.api.webmodule.WebProjectConstants;
import org.netbeans.modules.web.browser.spi.ProjectBrowserProvider;
import org.netbeans.modules.web.common.api.CssPreprocessors;
import org.netbeans.modules.web.common.ui.api.CssPreprocessorsUI;
import org.netbeans.modules.web.project.api.WebProjectUtilities;
import org.netbeans.modules.web.project.classpath.ClassPathSupportCallbackImpl;
import org.netbeans.modules.web.project.classpath.DelagatingProjectClassPathModifierImpl;
import org.netbeans.modules.web.project.classpath.WebProjectLibrariesModifierImpl;
import org.netbeans.modules.web.project.spi.BrokenLibraryRefFilter;
import org.netbeans.modules.web.project.spi.BrokenLibraryRefFilterProvider;
import org.netbeans.modules.web.project.ui.customizer.CustomizerProviderImpl;
import org.netbeans.modules.web.spi.webmodule.WebModuleImplementation;
import org.netbeans.modules.web.spi.webmodule.WebModuleImplementation2;
import org.netbeans.modules.web.spi.webmodule.WebPrivilegedTemplates;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.modules.websvc.api.webservices.WebServicesSupport;
import org.netbeans.modules.websvc.api.client.WebServicesClientSupport;
import org.netbeans.modules.websvc.spi.webservices.WebServicesSupportFactory;
import org.netbeans.spi.java.project.support.ExtraSourceJavadocSupport;
import org.netbeans.spi.java.project.support.LookupMergerSupport;
import org.netbeans.spi.java.project.support.ui.BrokenReferencesSupport;
import org.netbeans.spi.whitelist.support.WhiteListQueryMergerSupport;
import org.netbeans.spi.project.support.ant.PropertyProvider;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.netbeans.spi.queries.FileEncodingQueryImplementation;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileSystem.AtomicAction;
import org.openide.loaders.DataObject;
import org.openide.modules.Places;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.ProxyLookup;
import org.openide.windows.WindowManager;
import org.openide.windows.WindowSystemEvent;
import org.openide.windows.WindowSystemListener;

/**
 * Represents one plain Web project.
 * @author Jesse Glick, et al., Pavel Buzek
 * @author kaktus
 */
public final class WebProject implements Project {

    private static final Logger LOGGER = Logger.getLogger(WebProject.class.getName());

    private static final Icon WEB_PROJECT_ICON = ImageUtilities.loadImageIcon("org/netbeans/modules/web/project/ui/resources/webProjectIcon.gif", false); // NOI18

    private static final Pattern TLD_PATTERN = Pattern.compile("(META-INF/.*\\.tld)|(META-INF/tlds/.*\\.tld)");

    private final AntProjectHelper helper;
    private final PropertyEvaluator eval;
    private final ReferenceHelper refHelper;
    private final GeneratedFilesHelper genFilesHelper;
    private Lookup lookup;
    private final ProjectWebModule webModule;
    private final CopyOnSaveSupport copyOnSaveSupport;
    private final ClientSideDevelopmentSupport easelSupport;
    private final ArtifactCopyOnSaveSupport artifactSupport;
    private final DeployOnSaveSupport deployOnSaveSupport;
    private final EjbJarProvider webEjbJarProvider;
    private final EjbJar apiEjbJar;
    private WebModule apiWebModule;
    private WebServicesSupport apiWebServicesSupport;
    private JAXWSSupport apiJaxwsSupport;
    private WebServicesClientSupport apiWebServicesClientSupport;
    private JAXWSClientSupport apiJAXWSClientSupport;
    private WebContainerImpl enterpriseResourceSupport;
    private FileWatch webPagesFileWatch;
    private FileWatch webInfFileWatch;
    private PropertyChangeListener j2eePlatformListener;
    private PropertyChangeListener enterpriseBeansListener;
    private SourceRoots sourceRoots;
    private SourceRoots testRoots;
    private final UpdateHelper updateHelper;
    private final UpdateProjectImpl updateProject;
    private final AuxiliaryConfiguration aux;
    private final DelagatingProjectClassPathModifierImpl cpMod;
    private final WebProjectLibrariesModifierImpl libMod;
    private final ClassPathProviderImpl cpProvider;
    private ClassPathUiSupport.Callback classPathUiSupportCallback;
    private WhiteListUpdater whiteListUpdater;
    private CssPreprocessorsSupport cssSupport;

    private AntBuildExtender buildExtender;

    // set to true when project customizer is being closed and changes persisted
    private final ThreadLocal<Boolean> projectPropertiesSave;

    // #233052
    private final WindowSystemListener windowSystemListener = new WindowSystemListener() {

        @Override
        public void beforeLoad(WindowSystemEvent event) {
        }

        @Override
        public void afterLoad(WindowSystemEvent event) {
        }

        @Override
        public void beforeSave(WindowSystemEvent event) {
            easelSupport.close();
        }

        @Override
        public void afterSave(WindowSystemEvent event) {
        }

    };

    private class FileWatch implements AntProjectListener, FileChangeListener {

        private String propertyName;

        private FileObject fileObject = null;
        private boolean watchRename = false;

        public FileWatch(String property) {
            this.propertyName = property;
        }

        public void init() {
            helper.addAntProjectListener(this);
            updateFileChangeListener();
        }

        public void reset() {
            helper.removeAntProjectListener(this);
            setFileObject(null);
        }

        public void updateFileChangeListener() {
            File resolvedFile;
            FileObject fo = null;
            String propertyValue = helper.getStandardPropertyEvaluator().getProperty(propertyName);
            if (propertyValue != null) {
                String resolvedPath = helper.resolvePath(propertyValue);
                resolvedFile = new File(resolvedPath).getAbsoluteFile();
                File f = resolvedFile;
                while (f != null && (fo = FileUtil.toFileObject(f)) == null) {
                    f = f.getParentFile();
                }
                watchRename = f == resolvedFile;
            } else {
                resolvedFile = null;
                watchRename = false;
            }
            setFileObject(fo);
        }

        private void setFileObject(FileObject fo) {
            if (!isEqual(fo, fileObject)) {
                if (fileObject != null) {
                    fileObject.removeFileChangeListener(this);
                }
                fileObject = fo;
                if (fileObject != null) {
                    fileObject.addFileChangeListener(this);
                }
            }
        }

        private boolean isEqual(Object object1, Object object2) {
            if (object1 == object2) {
                return true;
            }
            if(object1 == null) {
                return false;
            }
            return object1.equals(object2);
        }

        // AntProjectListener

        @Override
        public void configurationXmlChanged(AntProjectEvent ev) {
            updateFileChangeListener();
        }

        @Override
        public void propertiesChanged(AntProjectEvent ev) {
            updateFileChangeListener();
        }

        // FileChangeListener

        @Override
        public void fileFolderCreated(FileEvent fe) {
            updateFileChangeListener();
        }

        @Override
        public void fileDataCreated(FileEvent fe) {
            updateFileChangeListener();
        }

        @Override
        public void fileChanged(FileEvent fe) {
            updateFileChangeListener();
        }

        @Override
        public void fileDeleted(FileEvent fe) {
            updateFileChangeListener();
        }

        @Override
        public void fileRenamed(final FileRenameEvent fe) {
            if(watchRename && fileObject.isValid()) {
                String prop = helper.getStandardPropertyEvaluator().getProperty(propertyName);
                if (prop == null) {
                    return;
                }
                final File f = new File(prop);
                if(f.getName().equals(fe.getName())) {
                    ProjectManager.mutex().postWriteRequest(new Runnable() {
                        public void run() {
                            EditableProperties properties = new EditableProperties(true);
                            properties.setProperty(propertyName, new File(f.getParentFile(), fe.getFile().getName()).getPath());
                            Utils.updateProperties(helper, AntProjectHelper.PROJECT_PROPERTIES_PATH, properties);
                            try {
                                ProjectManager.getDefault().saveProject(WebProject.this);
                                updateFileChangeListener();
                            } catch (IOException ex) {
                                Exceptions.printStackTrace(ex);
                            } catch (IllegalArgumentException ex) {
                                Exceptions.printStackTrace(ex);
                            }
                        }
                    });
                }
            }
            updateFileChangeListener();
        }

        @Override
        public void fileAttributeChanged(FileAttributeEvent fe) {
        }
    };

    WebProject(final AntProjectHelper helper) throws IOException {
        this.projectPropertiesSave = new ThreadLocal<Boolean>() {
            @Override
            protected Boolean initialValue() {
                return Boolean.FALSE;
            }
        };
        this.helper = helper;
        aux = helper.createAuxiliaryConfiguration();
        updateProject = new UpdateProjectImpl(this, this.helper, aux);
        this.updateHelper = new UpdateHelper(updateProject, helper);
        updateProject.setUpdateHelper(updateHelper);
        eval = createEvaluator();
        refHelper = new ReferenceHelper(helper, aux, eval);
        buildExtender = AntBuildExtenderFactory.createAntExtender(new WebExtenderImplementation(), refHelper);
        genFilesHelper = new GeneratedFilesHelper(helper, buildExtender);
        this.cpProvider = new ClassPathProviderImpl(this.helper, evaluator(), getSourceRoots(),getTestSourceRoots());
        webModule = new ProjectWebModule (this, updateHelper, cpProvider);
        apiWebModule = WebModuleFactory.createWebModule(new WebModuleImpl2(webModule));
        webEjbJarProvider = new EjbJarProvider(webModule, cpProvider);
        apiEjbJar = EjbJarFactory.createEjbJar(webEjbJarProvider);
        WebProjectWebServicesSupport webProjectWebServicesSupport = new WebProjectWebServicesSupport(this, helper, refHelper);
        WebProjectJAXWSSupport jaxwsSupport = new WebProjectJAXWSSupport(this, helper);
        WebProjectJAXWSClientSupport jaxWsClientSupport = new WebProjectJAXWSClientSupport(this, helper);
        WebProjectWebServicesClientSupport webProjectWebServicesClientSupport = new WebProjectWebServicesClientSupport(this, helper, refHelper);
        apiWebServicesSupport = WebServicesSupportFactory.createWebServicesSupport (webProjectWebServicesSupport);
        apiJaxwsSupport = JAXWSSupportFactory.createJAXWSSupport(jaxwsSupport);
        apiWebServicesClientSupport = WebServicesClientSupportFactory.createWebServicesClientSupport (webProjectWebServicesClientSupport);
        apiJAXWSClientSupport = JAXWSClientSupportFactory.createJAXWSClientSupport(jaxWsClientSupport);
        enterpriseResourceSupport = new WebContainerImpl(this, refHelper, helper);
        ClassPathModifier cpModTemp = new ClassPathModifier(this, this.updateHelper, eval, refHelper,
            new ClassPathSupportCallbackImpl(helper), createClassPathModifierCallback(), getClassPathUiSupportCallback());
        libMod = new WebProjectLibrariesModifierImpl(this, this.updateHelper, eval, refHelper);
        cpMod = new DelagatingProjectClassPathModifierImpl(cpModTemp, libMod);
        easelSupport = ClientSideDevelopmentSupport.createInstance(this, WebProjectType.TYPE, Utils.USG_LOGGER_NAME);
        cssSupport = new CssPreprocessorsSupport(this);
        lookup = createLookup(aux, cpProvider);
        copyOnSaveSupport = new CopyOnSaveSupport();
        artifactSupport = new ArtifactCopySupport();
        deployOnSaveSupport = new DeployOnSaveSupportProxy();
        webPagesFileWatch = new FileWatch(WebProjectProperties.WEB_DOCBASE_DIR);
        webInfFileWatch = new FileWatch(WebProjectProperties.WEBINF_DIR);
        // whitelist updater listens on project properties and pays attention to whitelist changes
        whiteListUpdater = new WhiteListUpdaterImpl(this);
        WindowManager windowManager = WindowManager.getDefault();
        windowManager.addWindowSystemListener(WeakListeners.create(WindowSystemListener.class, windowSystemListener, windowManager));
    }

    public void setProjectPropertiesSave(boolean value) {
        this.projectPropertiesSave.set(value);
    }

    public DelagatingProjectClassPathModifierImpl getClassPathModifier() {
        return cpMod;
    }

    public WebProjectLibrariesModifierImpl getLibrariesModifier() {
        return libMod;
    }

    public DeployOnSaveSupport getDeployOnSaveSupport() {
        return deployOnSaveSupport;
    }

    private ClassPathModifier.Callback createClassPathModifierCallback() {
        return new ClassPathModifier.Callback() {
            public String getClassPathProperty(SourceGroup sg, String type) {
                assert sg != null : "SourceGroup cannot be null";  //NOI18N
                assert type != null : "Type cannot be null";  //NOI18N
                final String[] classPathProperty = getClassPathProvider().getPropertyName (sg, type);
                if (classPathProperty == null || classPathProperty.length == 0) {
                    throw new UnsupportedOperationException ("Modification of [" + sg.getRootFolder().getPath() +", " + type + "] is not supported"); //NOI18N
                }
                assert !classPathProperty[0].equals(WebProjectProperties.J2EE_PLATFORM_CLASSPATH);
                return classPathProperty[0];
            }

            public String getElementName(String classpathProperty) {
                if (ProjectProperties.JAVAC_CLASSPATH.equals(classpathProperty)) {
                    return ClassPathSupportCallbackImpl.TAG_WEB_MODULE_LIBRARIES;
                }
                return null;
            }
        };
    }

    public synchronized ClassPathUiSupport.Callback getClassPathUiSupportCallback() {
        if (classPathUiSupportCallback == null) {
            classPathUiSupportCallback = new ClassPathUiSupport.Callback() {
                public void initItem(ClassPathSupport.Item item) {
                    switch (item.getType()) {
                        case ClassPathSupport.Item.TYPE_JAR:
                            item.setAdditionalProperty(ClassPathSupportCallbackImpl.PATH_IN_DEPLOYMENT,
                                    item.getResolvedFile().isDirectory() ?
                                        ClassPathSupportCallbackImpl.PATH_IN_WAR_DIR :
                                        ClassPathSupportCallbackImpl.PATH_IN_WAR_LIB);
                            break;
                        case ClassPathSupport.Item.TYPE_LIBRARY:
                            if (item.getLibrary().getType().equals(J2eePlatform.LIBRARY_TYPE)) {
                                break;
                            }
                            item.setAdditionalProperty(ClassPathSupportCallbackImpl.PATH_IN_DEPLOYMENT,
                                    Utils.isLibraryDirectoryBased(item) ?
                                        ClassPathSupportCallbackImpl.PATH_IN_WAR_DIR :
                                        ClassPathSupportCallbackImpl.PATH_IN_WAR_LIB);
                            break;
                        default:
                            item.setAdditionalProperty(ClassPathSupportCallbackImpl.PATH_IN_DEPLOYMENT,
                                    ClassPathSupportCallbackImpl.PATH_IN_WAR_LIB);
                    }
                }
            };

        }
        return classPathUiSupportCallback;
    }

    public UpdateProjectImpl getUpdateImplementation() {
        return updateProject;
    }

    public FileObject getProjectDirectory() {
        return helper.getProjectDirectory();
    }

    public UpdateHelper getUpdateHelper() {
        return updateHelper;
    }

    public String toString() {
        return "WebProject[" + getProjectDirectory() + "]"; // NOI18N
    }

    private PropertyEvaluator createEvaluator() {
        // XXX might need to add a custom evaluator to handle active platform substitutions... TBD
        helper.getStandardPropertyEvaluator();//workaround for issue for #181253, need to call before custom creation
        PropertyEvaluator baseEval2 = PropertyUtils.sequentialPropertyEvaluator(
                helper.getStockPropertyPreprovider(),
                helper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH));
        return PropertyUtils.sequentialPropertyEvaluator(
                helper.getStockPropertyPreprovider(),
                helper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH),
                helper.getProjectLibrariesPropertyProvider(),
                PropertyUtils.userPropertiesProvider(baseEval2,
                    "user.properties.file", FileUtil.toFile(getProjectDirectory())), // NOI18N
                helper.getPropertyProvider(AntProjectHelper.PROJECT_PROPERTIES_PATH),
                UPDATE_PROPERTIES);
    }

    private static final PropertyProvider UPDATE_PROPERTIES;

    static {
        Map<String, String> defs = new HashMap<String, String>();

        defs.put(ProjectProperties.ANNOTATION_PROCESSING_ENABLED, "true"); //NOI18N
        defs.put(ProjectProperties.ANNOTATION_PROCESSING_ENABLED_IN_EDITOR, "true"); //NOI18N
        defs.put(ProjectProperties.ANNOTATION_PROCESSING_RUN_ALL_PROCESSORS, "true"); //NOI18N
        defs.put(ProjectProperties.ANNOTATION_PROCESSING_PROCESSORS_LIST, ""); //NOI18N
        defs.put(ProjectProperties.ANNOTATION_PROCESSING_SOURCE_OUTPUT, "${build.generated.sources.dir}/ap-source-output"); //NOI18N
        defs.put(ProjectProperties.JAVAC_PROCESSORPATH,"${" + ProjectProperties.JAVAC_CLASSPATH + "}"); //NOI18N
        defs.put("javac.test.processorpath", "${" + ProjectProperties.JAVAC_TEST_CLASSPATH + "}"); // NOI18N

        UPDATE_PROPERTIES = PropertyUtils.fixedPropertyProvider(defs);
    }

    public PropertyEvaluator evaluator() {
        return eval;
    }

    public ReferenceHelper getReferenceHelper () {
        return this.refHelper;
    }

    public Lookup getLookup() {
        return lookup;
    }

    public AntProjectHelper getAntProjectHelper() {
        return helper;
    }

    private Lookup createLookup(AuxiliaryConfiguration aux, ClassPathProviderImpl cpProvider) {
        FileEncodingQueryImplementation encodingQuery = QuerySupport.createFileEncodingQuery(evaluator(), WebProjectProperties.SOURCE_ENCODING);
        final WebLogicalViewProvider lvp = new WebLogicalViewProvider(this, this.updateHelper, evaluator (), refHelper, webModule);
        Lookup base = Lookups.fixed(new Object[] {
            QuerySupport.createProjectInformation(updateHelper, this, WEB_PROJECT_ICON),
            aux,
            helper.createCacheDirectoryProvider(),
            helper.createAuxiliaryProperties(),
            refHelper.createSubprojectProvider(),
            new ProjectWebModuleProvider (),
            new ProjectWebServicesSupportProvider(),
            webModule, //implements J2eeModuleProvider
            // FIXME this is just fallback for code searching for the old SPI in lookup
            // remove in next release
            new WebModuleImpl(apiWebModule),
            enterpriseResourceSupport,
            lvp,
            new CustomizerProviderImpl(this, this.updateHelper, evaluator(), refHelper),
            LookupMergerSupport.createClassPathProviderMerger(cpProvider),
            QuerySupport.createCompiledSourceForBinaryQuery(helper, evaluator(), getSourceRoots(), getTestSourceRoots(),
                    new String[]{"build.classes.dir", "dist.war"}, new String[]{"build.test.classes.dir"}),
            QuerySupport.createJavadocForBinaryQuery(helper, evaluator(), new String[]{"build.classes.dir", "dist.war"}),
            QuerySupport.createAnnotationProcessingQuery(helper, eval, ProjectProperties.ANNOTATION_PROCESSING_ENABLED, ProjectProperties.ANNOTATION_PROCESSING_ENABLED_IN_EDITOR, ProjectProperties.ANNOTATION_PROCESSING_RUN_ALL_PROCESSORS, ProjectProperties.ANNOTATION_PROCESSING_PROCESSORS_LIST, ProjectProperties.ANNOTATION_PROCESSING_SOURCE_OUTPUT, ProjectProperties.ANNOTATION_PROCESSING_PROCESSOR_OPTIONS),
            new AntArtifactProviderImpl(),
            new ProjectXmlSavedHookImpl(),
            UILookupMergerSupport.createProjectOpenHookMerger(new ProjectOpenedHookImpl()),
            QuerySupport.createUnitTestForSourceQuery(getSourceRoots(), getTestSourceRoots()),
            QuerySupport.createSourceLevelQuery2(evaluator()),
            QuerySupport.createSources(this, helper, evaluator(),
                    getSourceRoots(),
                    getTestSourceRoots(),
                    Roots.propertyBased(new String[] {WebProjectProperties.SOURCE_ROOT}, new String[] {NbBundle.getMessage(WebProject.class, "LBL_Node_WebModule")}, false, null, null),
                    Roots.propertyBased(new String[] {WebProjectProperties.WEB_DOCBASE_DIR}, new String[] {NbBundle.getMessage(WebProject.class, "LBL_Node_DocBase")}, true, WebProjectConstants.TYPE_DOC_ROOT, null),
                    Roots.propertyBased(new String[] {WebProjectProperties.WEBINF_DIR}, new String[] {NbBundle.getMessage(WebProject.class, "LBL_Node_WebInf")}, false, WebProjectConstants.TYPE_WEB_INF, null),
                    Roots.nonSourceRoots(ProjectProperties.BUILD_DIR, WebProjectProperties.DIST_DIR)),
            QuerySupport.createSharabilityQuery2(helper, evaluator(), getSourceRoots(),
                getTestSourceRoots(), WebProjectProperties.WEB_DOCBASE_DIR),
            LookupProviderSupport.createSharabilityQueryMerger(),
            new RecommendedTemplatesImpl(this),
            new CoSAwareFileBuiltQueryImpl(QuerySupport.createFileBuiltQuery(helper, evaluator(), getSourceRoots(), getTestSourceRoots()), this),
            ProjectClassPathModifier.extenderForModifier(cpMod),
            buildExtender,
            cpMod,
            cpMod.getClassPathModifier(),
            new WebProjectOperations(this),
            new WebPersistenceProvider(this, evaluator(), cpProvider),
            new PersistenceProviderSupplierImpl(this),
            EntityManagerGenerationStrategyResolverFactory.createInstance(this),
            new WebJPADataSourceSupport(this),
            ServerUtil.createServerStatusProvider(getWebModule()),
            new WebJPAModuleInfo(this),
            new WebJPATargetInfo(this),
            UILookupMergerSupport.createPrivilegedTemplatesMerger(),
            UILookupMergerSupport.createRecommendedTemplatesMerger(),
            LookupProviderSupport.createSourcesMerger(),
            LookupProviderSupport.createActionProviderMerger(),
            WhiteListQueryMergerSupport.createWhiteListQueryMerger(),
            new WebPropertyEvaluatorImpl(evaluator()),
            WebProject.this, // never cast an externally obtained Project to WebProject - use lookup instead
            libMod,
            encodingQuery,
            new CreateFromTemplateAttributesImpl(helper, encodingQuery),
            ExtraSourceJavadocSupport.createExtraSourceQueryImplementation(this, helper, eval),
            LookupMergerSupport.createSFBLookupMerger(),
            ExtraSourceJavadocSupport.createExtraJavadocQueryImplementation(this, helper, eval),
            LookupMergerSupport.createJFBLookupMerger(),
            QuerySupport.createBinaryForSourceQueryImplementation(getSourceRoots(), getTestSourceRoots(), helper, eval),
            new ProjectWebRootProviderImpl(this),
            easelSupport,
            new JavaEEProjectSettingsImpl(this),
            BrokenReferencesSupport.createReferenceProblemsProvider(helper, refHelper, eval, lvp.getBreakableProperties(), lvp.getPlatformProperties()),
            BrokenReferencesSupport.createPlatformVersionProblemProvider(helper, eval, PlatformUpdatedCallBackImpl.create(WebProjectType.PROJECT_CONFIGURATION_NAMESPACE, updateHelper), JavaPlatform.getDefault().getSpecification().getName(), ProjectProperties.PLATFORM_ACTIVE, ProjectProperties.JAVAC_SOURCE, ProjectProperties.JAVAC_TARGET),
            CssPreprocessorsUI.getDefault().createProjectProblemsProvider(this),
            UILookupMergerSupport.createProjectProblemsProviderMerger(),
        });

        Lookup ee6 = Lookups.fixed(new Object[]{
            EjbJarSupport.createEjbJarProvider(this, apiEjbJar),
            EjbJarSupport.createEjbJarsInProject(apiEjbJar)
        });

        WebProjectLookup wpl = new WebProjectLookup(this, base, ee6, new WebProjectBrowserProvider(this));
        eval.addPropertyChangeListener(wpl);
        lookup = wpl;
        return LookupProviderSupport.createCompositeLookup(lookup, "Projects/org-netbeans-modules-web-project/Lookup"); //NOI18N
    }

    public ClassPathProviderImpl getClassPathProvider () {
        return this.cpProvider;
    }

    String getBuildXmlName () {
        String storedName = helper.getStandardPropertyEvaluator ().getProperty (WebProjectProperties.BUILD_FILE);
        return storedName == null ? GeneratedFilesHelper.BUILD_XML_PATH : storedName;
    }

    // Package private methods -------------------------------------------------

    /**
     * Returns the source roots of this project
     * @return project's source roots
     */
    public synchronized SourceRoots getSourceRoots() {
        if (this.sourceRoots == null) { //Local caching, no project metadata access
            this.sourceRoots = SourceRoots.create(updateHelper, evaluator(), getReferenceHelper(), WebProjectType.PROJECT_CONFIGURATION_NAMESPACE, "source-roots", false, "src.{0}{1}.dir"); //NOI18N
        }
        return this.sourceRoots;
    }

    public synchronized SourceRoots getTestSourceRoots() {
        if (this.testRoots == null) { //Local caching, no project metadata access
            this.testRoots = SourceRoots.create(this.updateHelper, evaluator(), getReferenceHelper(), WebProjectType.PROJECT_CONFIGURATION_NAMESPACE, "test-roots", true, "test.{0}{1}.dir"); //NOI18N
        }
        return this.testRoots;
    }

    File getTestClassesDirectory() {
        String testClassesDir = evaluator().getProperty(ProjectProperties.BUILD_TEST_CLASSES_DIR);
        if (testClassesDir == null) {
            return null;
        }
        return helper.resolveFile(testClassesDir);
    }

    public ProjectWebModule getWebModule () {
        return webModule;
    }

    public WebModule getAPIWebModule () {
        return apiWebModule;
    }

    public EjbJar getAPIEjbJar() {
        return apiEjbJar;
    }

    WebServicesSupport getAPIWebServicesSupport () {
            return apiWebServicesSupport;
    }

    JAXWSSupport getAPIJAXWSSupport () {
            return apiJaxwsSupport;
    }

    WebServicesClientSupport getAPIWebServicesClientSupport () {
            return apiWebServicesClientSupport;
    }

    JAXWSClientSupport getAPIJAXWSClientSupport () {
            return apiJAXWSClientSupport;
    }

    /** Return configured project name. */
    public String getName() {
        return ProjectUtils.getInformation(this).getName();
    }

    /** Store configured project name. */
    public void setName(final String name) {
        ProjectManager.mutex().writeAccess(new Mutex.Action<Void>() {
            public Void run() {
                Element data = helper.getPrimaryConfigurationData(true);
                // XXX replace by XMLUtil when that has findElement, findText, etc.
                NodeList nl = data.getElementsByTagNameNS(WebProjectType.PROJECT_CONFIGURATION_NAMESPACE, "name");
                Element nameEl;
                if (nl.getLength() == 1) {
                    nameEl = (Element) nl.item(0);
                    NodeList deadKids = nameEl.getChildNodes();
                    while (deadKids.getLength() > 0) {
                        nameEl.removeChild(deadKids.item(0));
                    }
                } else {
                    nameEl = data.getOwnerDocument().createElementNS(WebProjectType.PROJECT_CONFIGURATION_NAMESPACE, "name");
                    data.insertBefore(nameEl, /* OK if null */data.getChildNodes().item(0));
                }
                nameEl.appendChild(data.getOwnerDocument().createTextNode(name));
                helper.putPrimaryConfigurationData(data, true);
                return null;
            }
        });
    }

    public void registerJ2eePlatformListener(final J2eePlatform platform) {
        // listen to classpath changes
        j2eePlatformListener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals(J2eePlatform.PROP_CLASSPATH)) {
                    updateClasspath(platform);
                }
            }
        };
        platform.addPropertyChangeListener(j2eePlatformListener);
    }

    public void unregisterJ2eePlatformListener(J2eePlatform platform) {
        if (j2eePlatformListener != null) {
            platform.removePropertyChangeListener(j2eePlatformListener);
        }
    }

    private void updateClasspath(final J2eePlatform platform) {
        ProjectManager.mutex().writeAccess(new Mutex.Action<Void>() {
            public Void run() {
                EditableProperties ep = helper.getProperties(
                        AntProjectHelper.PRIVATE_PROPERTIES_PATH);
                EditableProperties projectProps = helper.getProperties(
                        AntProjectHelper.PROJECT_PROPERTIES_PATH);

                    Map<String, String> roots = J2EEProjectProperties.extractPlatformLibrariesRoot(platform);
                    String classpath = J2EEProjectProperties.toClasspathString(
                            ClasspathUtil.getJ2eePlatformClasspathEntries(WebProject.this, null), roots);
                    if (roots != null) {
                        projectProps.setProperty(WebProjectProperties.J2EE_PLATFORM_CLASSPATH, classpath);
                    } else {
                        ep.setProperty(WebProjectProperties.J2EE_PLATFORM_CLASSPATH, classpath);
                    }
                    helper.putProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH, ep);
                    helper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, projectProps);
                    try {
                        ProjectManager.getDefault().saveProject(WebProject.this);
                    } catch (IOException e) {
                        Exceptions.printStackTrace(e);
                    }
                return null;
            }
        });
    }

    // Private innerclasses ----------------------------------------------------

    public static void makeSureProjectHasJspCompilationLibraries(final ReferenceHelper refHelper) {
        if (refHelper.getProjectLibraryManager() == null) {
            return;
        }
        ProjectManager.mutex().writeAccess(new Runnable() {

            public void run() {
                Library lib = refHelper.getProjectLibraryManager().getLibrary("jsp-compilation");

                // #198056 - force libraries update by deleting old versions first:
                if (lib != null) {
                    boolean oldNB69ver = lib.getURIContent("classpath").toString().contains("glassfish-jspparser-2.0.jar");
                    if (oldNB69ver) {
                        try {
                            refHelper.getProjectLibraryManager().removeLibrary(lib);
                            lib = refHelper.getProjectLibraryManager().getLibrary("jsp-compiler");
                            refHelper.getProjectLibraryManager().removeLibrary(lib);
                        } catch (IOException ex) {
                            Exceptions.printStackTrace(ex);
                        } catch (IllegalArgumentException ex) {
                            Exceptions.printStackTrace(ex);
                        }
                    }
                }

                lib = refHelper.getProjectLibraryManager().getLibrary("jsp-compiler");
                if (lib == null) {
                    try {
                        refHelper.copyLibrary(LibraryManager.getDefault().getLibrary("jsp-compiler")); // NOI18N
                    } catch (IOException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
                lib = refHelper.getProjectLibraryManager().getLibrary("jsp-compilation");
                if (lib == null) {
                    try {
                        refHelper.copyLibrary(LibraryManager.getDefault().getLibrary("jsp-compilation")); // NOI18N
                    } catch (IOException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
                lib = refHelper.getProjectLibraryManager().getLibrary("jsp-compilation-syscp");
                if (lib == null) {
                    try {
                        refHelper.copyLibrary(LibraryManager.getDefault().getLibrary("jsp-compilation-syscp")); // NOI18N
                    } catch (IOException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            }
        });
    }

    private final class ProjectXmlSavedHookImpl extends ProjectXmlSavedHook {

        ProjectXmlSavedHookImpl() {}

        protected void projectXmlSaved() throws IOException {
            int state = genFilesHelper.getBuildScriptState(
                GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
                WebProject.class.getResource("resources/build-impl.xsl"));
            final Boolean projectPropertiesSave = WebProject.this.projectPropertiesSave.get();
            if ((projectPropertiesSave && (state & GeneratedFilesHelper.FLAG_MODIFIED) == GeneratedFilesHelper.FLAG_MODIFIED) ||
                state == (GeneratedFilesHelper.FLAG_UNKNOWN | GeneratedFilesHelper.FLAG_MODIFIED |
                    GeneratedFilesHelper.FLAG_OLD_PROJECT_XML | GeneratedFilesHelper.FLAG_OLD_STYLESHEET)) {  //missing genfiles.properties
                try {
                    AntProjectUtil.backupBuildImplFile(updateHelper);
                    genFilesHelper.generateBuildScriptFromStylesheet(
                            GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
                            WebProject.class.getResource("resources/build-impl.xsl"));
                } catch (IOException e) {
                    Exceptions.printStackTrace(e);
                } catch (IllegalStateException e) {
                    Exceptions.printStackTrace(e);
                }
            } else {
                genFilesHelper.refreshBuildScript(GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
                                                  org.netbeans.modules.web.project.WebProject.class.getResource("resources/build-impl.xsl"),
                                                  false);
            }
            genFilesHelper.refreshBuildScript(
                getBuildXmlName (),
                WebProject.class.getResource("resources/build.xsl"),false);
        }

    }

    final class ProjectOpenedHookImpl extends ProjectOpenedHook {

        ProjectOpenedHookImpl() {}

        @Override
        @NbBundle.Messages("ERR_ProjectReadOnly=The project folder is read-only.")
        protected void projectOpened() {
            evaluator().addPropertyChangeListener(WebProject.this.webModule);

            WebLogicalViewProvider logicalViewProvider = WebProject.this.getLookup().lookup(WebLogicalViewProvider.class);
            if (logicalViewProvider != null) {
                logicalViewProvider.initialize();
            }

            try {
                getProjectDirectory().getFileSystem().runAtomicAction(new AtomicAction() {
                    public void run() throws IOException {
                        ProjectManager.mutex().writeAccess(new Runnable() {
                            public void run()  {
                                updateProject();
                            }
                        });
                    }
                });
            } catch (IOException e) {
                // #222721 - Provide a better error message in case of read-only location of project.
                if (!WebProject.this.getProjectDirectory().canWrite()) {
                    NotifyDescriptor nd = new NotifyDescriptor.Message(Bundle.ERR_ProjectReadOnly());
                    DialogDisplayer.getDefault().notify(nd);
                } else {
                    Logger.getLogger("global").log(Level.INFO, null, e);
                }
            }

            try {

                if (webModule.getDeploymentDescriptor() == null
                        && webModule.getConfigSupport().isDescriptorRequired()) {
                    DDHelper.createWebXml(webModule.getJ2eeProfile(), webModule.getWebInf());
                }

                //DDDataObject initialization to be ready to listen on changes (#45771)

                // web.xml
                try {
                    FileObject ddFO = webModule.getDeploymentDescriptor();
                    if (ddFO != null) {
                        DataObject dobj = DataObject.find(ddFO);
                    }
                } catch (org.openide.loaders.DataObjectNotFoundException ex) {
                    //PENDING
                }

                // ejb-jar.xml
                try {
                    FileObject ejbDdFO = webEjbJarProvider.getDeploymentDescriptor();
                    if (ejbDdFO != null) {
                        DataObject ejbdobj = DataObject.find(ejbDdFO);
                    }
                } catch (org.openide.loaders.DataObjectNotFoundException ex) {
                    //PENDING`
                }

                // Register copy on save support
                copyOnSaveSupport.initialize();

                // Check up on build scripts.
                if (updateHelper.isCurrent()) {
                    int flags = genFilesHelper.getBuildScriptState(
                        GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
                        WebProject.class.getResource("resources/build-impl.xsl"));
                    if ((flags & GeneratedFilesHelper.FLAG_MODIFIED) != 0
                        && (flags & GeneratedFilesHelper.FLAG_OLD_PROJECT_XML) != 0) {
                            AntProjectUtil.backupBuildImplFile(updateHelper);
                            genFilesHelper.generateBuildScriptFromStylesheet(
                                GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
                                WebProject.class.getResource("resources/build-impl.xsl"));
                    } else {
                        genFilesHelper.refreshBuildScript(
                            GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
                            WebProject.class.getResource("resources/build-impl.xsl"), true);
                    }
                    genFilesHelper.refreshBuildScript(
                        getBuildXmlName(),
                        WebProject.class.getResource("resources/build.xsl"), true);

                    String servInstID = evaluator().getProperty(WebProjectProperties.J2EE_SERVER_INSTANCE);
                    String serverType = null;
                    J2eePlatform platform = Deployment.getDefault().getJ2eePlatform(servInstID);
                    if (platform != null) {
                        // updates j2ee.platform.cp & wscompile.cp & reg. j2ee platform listener
                        WebProjectProperties.setServerInstance(WebProject.this, WebProject.this.updateHelper, servInstID);
                    } else {
                        // if there is some server instance of the type which was used
                        // previously do not ask and use it
                        serverType = evaluator().getProperty(WebProjectProperties.J2EE_SERVER_TYPE);
                        if (serverType != null) {
                            String instanceID = J2EEProjectProperties.getMatchingInstance(serverType, Type.WAR, WebProject.this.getWebModule().getJ2eeProfile());
                            if (instanceID != null) {
                                WebProjectProperties.setServerInstanceInner(WebProject.this, WebProject.this.updateHelper, instanceID);
                                platform = Deployment.getDefault().getJ2eePlatform(instanceID);
                            }
                        }
                        if (platform == null) {
                            BrokenServerSupport.showAlert();
                        }
                    }
                    // UI Logging
                    Utils.logUI(NbBundle.getBundle(WebProject.class), "UI_WEB_PROJECT_OPENED", // NOI18N
                            new Object[] {(serverType != null ? serverType : Deployment.getDefault().getServerID(servInstID)), servInstID});
                    // Usage Logging
                    String serverName = ""; // NOI18N
                    try {
                        if (servInstID != null) {
                            serverName = Deployment.getDefault().getServerInstance(servInstID).getServerDisplayName();
                        }
                    }
                    catch (InstanceRemovedException ire) {
                        // do nothing
                    }
                    Profile profile = WebProject.this.getWebModule().getJ2eeProfile();
                    Utils.logUsage(WebProject.class, "USG_PROJECT_OPEN_WEB", new Object[] { serverName, profile }); // NOI18N
                }

            } catch (IOException e) {
                // #222721 - Provide a better error message in case of read-only location of project.
                if (!WebProject.this.getProjectDirectory().canWrite()) {
                    NotifyDescriptor nd = new NotifyDescriptor.Message(Bundle.ERR_ProjectReadOnly());
                    DialogDisplayer.getDefault().notify(nd);
                } else {
                    LOGGER.log(Level.INFO, null, e);
                }
            }

            webModule.getConfigSupport().addLibraryChangeListener(new ChangeListener() {

                @Override
                public void stateChanged(ChangeEvent e) {
                    String servInstID = evaluator().getProperty(WebProjectProperties.J2EE_SERVER_INSTANCE);
                    J2eePlatform platform = Deployment.getDefault().getJ2eePlatform(servInstID);
                    if (platform != null) {
                        updateClasspath(platform);
                    }
                }
            });

            if (BrokenServerLibrarySupport.isBroken(WebProject.this)) {
                BrokenServerLibrarySupport.fixOrShowAlert(WebProject.this, new Runnable() {
                    @Override
                    public void run() {
                        WebLogicalViewProvider viewProvider = WebProject.this.getLookup().lookup(
                                WebLogicalViewProvider.class);
                        if (viewProvider != null) {
                            viewProvider.testBroken();
                        }
                    }
                });
            }

            // register project's classpaths to GlobalPathRegistry
            GlobalPathRegistry.getDefault().register(ClassPath.BOOT, cpProvider.getProjectClassPaths(ClassPath.BOOT));
            GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, cpProvider.getProjectClassPaths(ClassPath.SOURCE));
            GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, cpProvider.getProjectClassPaths(ClassPath.COMPILE));

            // initialize the server configuration
            // it MUST BE called AFTER classpaths are registered to GlobalPathRegistry!
            // DDProvider (used here) needs classpath set correctly when resolving Java Extents for annotations
            webModule.getConfigSupport().ensureConfigurationReady();

            //check the config context path
            String ctxRoot = webModule.getContextPath ();
            if (ctxRoot == null) {
                String sysName = getProjectDirectory ().getName (); //NOI18N
                sysName = Utils.createDefaultContext(sysName); //NOI18N
                webModule.setContextPath (sysName);
            }
            try {
                getAPIEjbJar().getMetadataModel().runReadActionWhenReady(new MetadataModelAction<EjbJarMetadata, Void>() {
                    public Void run(EjbJarMetadata metadata) throws Exception {
                        enterpriseBeansListener = new EnterpriseBeansListener(WebProject.this);
                        metadata.getRoot().getEnterpriseBeans().addPropertyChangeListener(enterpriseBeansListener);
                        return null;
                    }
                });
            } catch (Exception ex) {
                Exceptions.printStackTrace(ex);
            }

            if (Boolean.parseBoolean(evaluator().getProperty(
                    WebProjectProperties.J2EE_COMPILE_ON_SAVE))) {
                Deployment.getDefault().enableCompileOnSaveSupport(webModule);
            }
            artifactSupport.enableArtifactSynchronization(true);

            if (logicalViewProvider != null &&  logicalViewProvider.hasBrokenLinks()) {
                ProjectProblems.showAlert(WebProject.this);
            }
            if(apiWebServicesSupport.isBroken(WebProject.this)) {
                apiWebServicesSupport.showBrokenAlert(WebProject.this);
            }
            else if(WebServicesClientSupport.isBroken(WebProject.this)) {
                WebServicesClientSupport.showBrokenAlert(WebProject.this);
            }
            webPagesFileWatch.init();
            webInfFileWatch.init();

            CssPreprocessors.getDefault().addCssPreprocessorsListener(cssSupport);
        }

        private void updateProject() {
            // Make it easier to run headless builds on the same machine at least.
            EditableProperties ep = updateHelper.getProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH);

            File buildProperties = new File(Places.getUserDirectory(), "build.properties"); // NOI18N
            ep.setProperty("user.properties.file", buildProperties.getAbsolutePath()); //NOI18N

            //remove jaxws.endorsed.dir property
            ep.remove("jaxws.endorsed.dir");

            filterBrokenLibraryRefs();

            EditableProperties props = updateHelper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);    //Reread the properties, PathParser changes them
            if (props.getProperty(WebProjectProperties.J2EE_DEPLOY_ON_SAVE) == null) {
                String server = evaluator().getProperty(WebProjectProperties.J2EE_SERVER_INSTANCE);
                props.setProperty(WebProjectProperties.J2EE_DEPLOY_ON_SAVE,
                    server == null ? "false" : DeployOnSaveUtils.isDeployOnSaveSupported(server));
            }

            if (props.getProperty(WebProjectProperties.J2EE_COMPILE_ON_SAVE) == null) {
                props.setProperty(WebProjectProperties.J2EE_COMPILE_ON_SAVE,
                        props.getProperty(WebProjectProperties.J2EE_DEPLOY_ON_SAVE));
            }

            // #134642 - use Ant task from copylibs library
            SharabilityUtility.makeSureProjectHasCopyLibsLibrary(helper, refHelper);

            // make sure that sharable project which requires JSP compilation has its own
            // copy of jsp-compiler and jsp-compilation library:
            if (helper.isSharableProject() && "true".equals(props.get(WebProjectProperties.COMPILE_JSPS))) {
                makeSureProjectHasJspCompilationLibraries(refHelper);
            }

            J2EEProjectProperties.removeObsoleteLibraryLocations(ep);
            J2EEProjectProperties.removeObsoleteLibraryLocations(props);

            //add webinf.dir required by 6.0 projects
            if (props.getProperty(WebProjectProperties.WEBINF_DIR) == null) {
                //we can do this because in previous versions WEB-INF was expected under docbase
                String web = props.get(WebProjectProperties.WEB_DOCBASE_DIR);
                props.setProperty(WebProjectProperties.WEBINF_DIR, web + "/WEB-INF"); //NOI18N
            }

            //add persistence.xml.dir introduced in 6.5 - see issue 143884 and 142164
            if (props.getProperty(WebProjectProperties.PERSISTENCE_XML_DIR) == null) {
                props.setProperty(WebProjectProperties.PERSISTENCE_XML_DIR, "${" + WebProjectProperties.CONF_DIR + "}");
            }



            updateHelper.putProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH, ep);

            // update a dual build directory project to use a single directory
            if (updateHelper.isCurrent()) { // #113297, #118187
                // this operation should be safe in future as well - of course if properties with the same name weren't re-introduced
                props.remove("build.ear.web.dir");      // used to be WebProjectProperties.BUILD_EAR_WEB_DIR    // NOI18N
                props.remove("build.ear.classes.dir");  // used to be WebProjectProperties.BUILD_EAR_CLASSES_DIR    // NOI18N
            }
            // check debug.classpath - can be done every time, whenever
            String debugClassPath = props.getProperty(WebProjectProperties.DEBUG_CLASSPATH);
            props.setProperty(WebProjectProperties.DEBUG_CLASSPATH, Utils.correctDebugClassPath(debugClassPath));

            if (!props.containsKey(ProjectProperties.INCLUDES)) {
                props.setProperty(ProjectProperties.INCLUDES, "**"); // NOI18N
            }
            if (!props.containsKey(ProjectProperties.EXCLUDES)) {
                props.setProperty(ProjectProperties.EXCLUDES, ""); // NOI18N
            }
            if (!props.containsKey("build.generated.sources.dir")) { // NOI18N
                props.setProperty("build.generated.sources.dir", "${build.dir}/generated-sources"); // NOI18N
            }

            if (!props.containsKey(ProjectProperties.ANNOTATION_PROCESSING_ENABLED))props.setProperty(ProjectProperties.ANNOTATION_PROCESSING_ENABLED, "true"); //NOI18N
            if (!props.containsKey(ProjectProperties.ANNOTATION_PROCESSING_ENABLED_IN_EDITOR))props.setProperty(ProjectProperties.ANNOTATION_PROCESSING_ENABLED_IN_EDITOR, "true"); //NOI18N
            if (!props.containsKey(ProjectProperties.ANNOTATION_PROCESSING_RUN_ALL_PROCESSORS))props.setProperty(ProjectProperties.ANNOTATION_PROCESSING_RUN_ALL_PROCESSORS, "true"); //NOI18N
            if (!props.containsKey(ProjectProperties.ANNOTATION_PROCESSING_PROCESSORS_LIST))props.setProperty(ProjectProperties.ANNOTATION_PROCESSING_PROCESSORS_LIST, ""); //NOI18N
            if (!props.containsKey(ProjectProperties.ANNOTATION_PROCESSING_SOURCE_OUTPUT))props.setProperty(ProjectProperties.ANNOTATION_PROCESSING_SOURCE_OUTPUT, "${build.generated.sources.dir}/ap-source-output"); //NOI18N
            if (!props.containsKey(ProjectProperties.JAVAC_PROCESSORPATH))props.setProperty(ProjectProperties.JAVAC_PROCESSORPATH,"${" + ProjectProperties.JAVAC_CLASSPATH + "}"); //NOI18N
            if (!props.containsKey("javac.test.processorpath"))props.setProperty("javac.test.processorpath", "${" + ProjectProperties.JAVAC_TEST_CLASSPATH + "}"); // NOI18N

            // #207149
            if (!props.containsKey(WebProjectProperties.J2EE_COPY_STATIC_FILES_ON_SAVE)) {
                boolean b = Boolean.parseBoolean(props.getProperty(WebProjectProperties.J2EE_COMPILE_ON_SAVE));
                props.setProperty(WebProjectProperties.J2EE_COPY_STATIC_FILES_ON_SAVE, b ? "true" : "false");
            }

            updateHelper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, props);

            try {
                ProjectManager.getDefault().saveProject(WebProject.this);
            } catch (IOException e) {
                // #222721 - Provide a better error message in case of read-only location of project.
                if (!WebProject.this.getProjectDirectory().canWrite()) {
                    NotifyDescriptor nd = new NotifyDescriptor.Message(Bundle.ERR_ProjectReadOnly());
                    DialogDisplayer.getDefault().notify(nd);
                } else {
                    Exceptions.printStackTrace(e);
                }
            }
        }

        /**
         * Filters the broken library references (see issue 110040).
         */
        private void filterBrokenLibraryRefs() {
            // filter the compilation CP
            EditableProperties props = updateHelper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
            List<ClassPathSupport.Item> toRemove = filterBrokenLibraryItems(cpMod.getClassPathSupport().itemsList(props.getProperty(ProjectProperties.JAVAC_CLASSPATH), WebProjectProperties.TAG_WEB_MODULE_LIBRARIES));
            if (!toRemove.isEmpty()) {
                LOGGER.log(Level.FINE, "Will remove broken classpath library references: " + toRemove);
                try {
                    ClassPathModifierSupport.handleLibraryClassPathItems(WebProject.this, getAntProjectHelper(), cpMod.getClassPathSupport(),
                            toRemove, ProjectProperties.JAVAC_CLASSPATH, WebProjectProperties.TAG_WEB_MODULE_LIBRARIES, ClassPathModifier.REMOVE, false);
                } catch (IOException e) {
                    // should only occur when passing true as the saveProject parameter which we are not doing here
                    Exceptions.printStackTrace(e);
                }
            }
            // filter the additional (packaged) items
            // need to re-read the properites as the handleLibraryClassPathItems() might have changed them
            props = updateHelper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
            toRemove = filterBrokenLibraryItems(libMod.getClassPathSupport().itemsList(props.getProperty(WebProjectProperties.WAR_CONTENT_ADDITIONAL), WebProjectProperties.TAG_WEB_MODULE__ADDITIONAL_LIBRARIES));
            if (!toRemove.isEmpty()) {
                LOGGER.log(Level.FINE, "Will remove broken additional library references: " + toRemove);
                try {
                    ClassPathModifierSupport.handleLibraryClassPathItems(WebProject.this, getAntProjectHelper(), cpMod.getClassPathSupport(),
                            toRemove, WebProjectProperties.WAR_CONTENT_ADDITIONAL, WebProjectProperties.TAG_WEB_MODULE__ADDITIONAL_LIBRARIES, ClassPathModifier.REMOVE, false);
                } catch (IOException e) {
                    // should only occur when passing true as the saveProject parameter which we are not doing here
                    Exceptions.printStackTrace(e);
                }
            }
        }

        private List<ClassPathSupport.Item> filterBrokenLibraryItems(List<ClassPathSupport.Item> items) {
            List<ClassPathSupport.Item> toRemove = new LinkedList<ClassPathSupport.Item>();
            Collection<? extends BrokenLibraryRefFilter> filters = null;
            for (ClassPathSupport.Item item : items) {
                if (!item.isBroken() || item.getType() != ClassPathSupport.Item.TYPE_LIBRARY) {
                    continue;
                }
                String libraryName = ClassPathSupport.getLibraryNameFromReference(item.getReference());
                LOGGER.log(Level.FINE, "Broken reference to library: " + libraryName);
                if (filters == null) {
                    // initializing the filters lazily because usually they will not be needed anyway
                    // (most projects have no broken references)
                    filters = createFilters(WebProject.this);
                }
                for (BrokenLibraryRefFilter filter : filters) {
                    if (filter.removeLibraryReference(libraryName)) {
                        LOGGER.log(Level.FINE, "Will remove broken reference to library " + libraryName + " because of filter " + filter.getClass().getName());
                        toRemove.add(item);
                        break;
                    }
                }
            }
            return toRemove;
        }

        private List<BrokenLibraryRefFilter> createFilters(Project project) {
            List<BrokenLibraryRefFilter> filters = new LinkedList<BrokenLibraryRefFilter>();
            for (BrokenLibraryRefFilterProvider provider : Lookups.forPath("Projects/org-netbeans-modules-web-project/BrokenLibraryRefFilterProviders").lookupAll(BrokenLibraryRefFilterProvider.class)) { // NOI18N
                BrokenLibraryRefFilter filter = provider.createFilter(project);
                if (filter != null) {
                    filters.add(filter);
                }
            }
            return filters;
        }

        @Override
        protected void projectClosed() {
            evaluator().removePropertyChangeListener(WebProject.this.webModule);

            webPagesFileWatch.reset();
            webInfFileWatch.reset();

            // listen to j2ee platform classpath changes
            String servInstID = evaluator().getProperty(WebProjectProperties.J2EE_SERVER_INSTANCE);
            if (servInstID != null) {
                try {
                    J2eePlatform platform = Deployment.getDefault().getServerInstance(servInstID).getJ2eePlatform();
                    if (platform != null) {
                        unregisterJ2eePlatformListener(platform);
                    }
                } catch (InstanceRemovedException ex) {
                    // ignore in this case
                }
            }

            if (enterpriseBeansListener != null){
                try {
                    getAPIEjbJar().getMetadataModel().runReadActionWhenReady(new MetadataModelAction<EjbJarMetadata, Void>() {
                        public Void run(EjbJarMetadata metadata) throws Exception {
                            metadata.getRoot().getEnterpriseBeans().removePropertyChangeListener(enterpriseBeansListener);
                            enterpriseBeansListener = null;
                            return null;
                        }
                    });
                } catch (Exception ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
            // remove ServiceListener from jaxWsModel
            //if (jaxWsModel!=null) jaxWsModel.removeServiceListener(jaxWsServiceListener);

            // Probably unnecessary, but just in case:
            try {
                ProjectManager.getDefault().saveProject(WebProject.this);
            } catch (IOException e) {
                // #222721 - Provide a better error message in case of read-only location of project.
                if (!WebProject.this.getProjectDirectory().canWrite()) {
                    NotifyDescriptor nd = new NotifyDescriptor.Message(Bundle.ERR_ProjectReadOnly());
                    DialogDisplayer.getDefault().notify(nd);
                } else {
                    Exceptions.printStackTrace(e);
                }
            }

            // Unregister copy on save support
            try {
                copyOnSaveSupport.cleanup();
            }
            catch (FileStateInvalidException e) {
                Logger.getLogger("global").log(Level.INFO, null, e);
            }

            artifactSupport.enableArtifactSynchronization(false);
            Deployment.getDefault().disableCompileOnSaveSupport(webModule);
            // TODO: dongmei: anything for EJBs???????

            // unregister project's classpaths to GlobalPathRegistry
            try {
                GlobalPathRegistry.getDefault().unregister(ClassPath.BOOT, cpProvider.getProjectClassPaths(ClassPath.BOOT));
                GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, cpProvider.getProjectClassPaths(ClassPath.SOURCE));
                GlobalPathRegistry.getDefault().unregister(ClassPath.COMPILE, cpProvider.getProjectClassPaths(ClassPath.COMPILE));
            } catch (IllegalArgumentException e) {
                // Classpath were not registered before unregistration
                LOGGER.log(Level.INFO, "While trying to close the project, it was not possible to unregister classpath items "
                        + "because they were not registered in the first place.", e);
            }

            CssPreprocessors.getDefault().removeCssPreprocessorsListener(cssSupport);

            easelSupport.close();
        }

    }

    /**
     * Exports the main JAR as an official build product for use from other scripts.
     * The type of the artifact will be {@link AntArtifact#TYPE_JAR}.
     */
    private final class AntArtifactProviderImpl implements AntArtifactProvider {

        public AntArtifact[] getBuildArtifacts() {
            return new AntArtifact[] {
                helper.createSimpleAntArtifact(WebProjectConstants.ARTIFACT_TYPE_WAR, "dist.war", evaluator(), "dist", "clean", WebProjectProperties.BUILD_FILE), // NOI18N
                helper.createSimpleAntArtifact(WebProjectConstants.ARTIFACT_TYPE_WAR_EAR_ARCHIVE, "dist.ear.war", evaluator(), "dist-ear", "clean-ear", WebProjectProperties.BUILD_FILE) // NOI18N
            };
        }

    }

    // List of primarily supported templates

    private static final String[] TYPES = new String[] {
        "java-classes",         // NOI18N
        "java-main-class",      // NOI18N
        "java-forms",           // NOI18N
        "java-beans",           // NOI18N
        "persistence",          // NOI18N
        "oasis-XML-catalogs",   // NOI18N
        "XML",                  // NOI18N
        "ant-script",           // NOI18N
        "ant-task",             // NOI18N
        "REST-clients",         // NOI18N
        "servlet-types",        // NOI18N
        "web-types",            // NOI18N
        "web-types-server",     // NOI18N
        "web-services",         // NOI18N
        "web-service-clients",  // NOI18N
        "wsdl",                 // NOI18N
        "junit",                // NOI18N
        "html5",                // NOI18N
        "simple-files"          // NOI18N
    };

    private static final String[] TYPES_EJB31 = new String[] {
        "ejb-types",            // NOI18N
        "ejb-types-server",     // NOI18N
        "ejb-types_3_0",        // NOI18N
        "ejb-types_3_1",        // NOI18N
        "ejb-types_3_1_full",   // NOI18N
        "ejb-deployment-descriptor", // NOI18N
    };

    private static final String[] TYPES_EJB31_LITE = new String[] {
        "ejb-types",            // NOI18N
        "ejb-types_3_0",        // NOI18N
        "ejb-types_3_1",        // NOI18N
        "ejb-deployment-descriptor", // NOI18N
    };

    private static final String[] TYPES_EJB32_LITE = new String[] {
        "ejb-types",            // NOI18N
        "ejb-types_3_0",        // NOI18N
        "ejb-types_3_1",        // NOI18N
        "ejb-types_3_2",        // NOI18N
        "ejb-deployment-descriptor", // NOI18N
    };

    private static final String[] TYPES_ARCHIVE = new String[] {
        "deployment-descriptor",          // NOI18N
        "XML",                            // NOI18N
    };

    private static final String[] PRIVILEGED_NAMES = new String[] {
        "Templates/JSP_Servlet/JSP.jsp",            // NOI18N
        "Templates/JSP_Servlet/Html.html",          // NOI18N
        "Templates/JSP_Servlet/Servlet.java",       // NOI18N
        "Templates/Classes/Class.java",             // NOI18N
        "Templates/Classes/Package",                // NOI18N
        "Templates/WebServices/WebService.java",    // NOI18N
        "Templates/WebServices/WebServiceClient",   // NOI18N
        "Templates/Other/Folder"                    // NOI18N
    };

    private static final String[] PRIVILEGED_NAMES_EE5 = new String[] {
        "Templates/JSP_Servlet/JSP.jsp",            // NOI18N
        "Templates/JSP_Servlet/Html.html",          // NOI18N
        "Templates/JSP_Servlet/Servlet.java",       // NOI18N
        "Templates/Classes/Class.java",             // NOI18N
        "Templates/Classes/Package",                // NOI18N
        "Templates/Persistence/Entity.java", // NOI18N
        "Templates/Persistence/RelatedCMP", // NOI18N
        "Templates/Persistence/JsfFromDB", // NOI18N
        "Templates/WebServices/WebService.java",    // NOI18N
        "Templates/WebServices/WebServiceFromWSDL.java",    // NOI18N
        "Templates/WebServices/WebServiceClient",   // NOI18N
        "Templates/WebServices/RestServicesFromEntities", // NOI18N
        "Templates/WebServices/RestServicesFromPatterns",  //NOI18N
        "Templates/Other/Folder"                   // NOI18N
    };

    private static final String[] PRIVILEGED_NAMES_EE6_FULL = new String[] {
        "Templates/J2EE/Session", // NOI18N
        "Templates/J2EE/Message", // NOI18N
        "Templates/J2EE/TimerSession"   // NOI18N
    };

    private static final String[] PRIVILEGED_NAMES_EE6_WEB = new String[] {
        "Templates/J2EE/Session"  // NOI18N
    };

    private static final String[] PRIVILEGED_NAMES_EE7_WEB = new String[] {
        "Templates/J2EE/Session",       // NOI18N
        "Templates/J2EE/TimerSession"   // NOI18N
    };

    private static final String[] PRIVILEGED_NAMES_ARCHIVE = new String[] {
        "Templates/JSP_Servlet/webXml"     // NOI18N  ---
    };

    // guarded by this, #115809
    private List<String> privilegedTemplatesEE5 = null;
    private List<String> privilegedTemplates = null;

    // Path where instances of privileged templates are registered
    private static final String WEBTEMPLATE_PATH = "j2ee/webtier/templates"; //NOI18N

    synchronized List<String> getPrivilegedTemplates() {
        ensureTemplatesInitialized();
        return privilegedTemplates;
    }

    synchronized List<String> getPrivilegedTemplatesEE5() {
        ensureTemplatesInitialized();
        return privilegedTemplatesEE5;
    }

    public synchronized void resetTemplates() {
        privilegedTemplates = null;
        privilegedTemplatesEE5 = null;
    }

    private void ensureTemplatesInitialized() {
        assert Thread.holdsLock(this);
        if (privilegedTemplates != null
                && privilegedTemplatesEE5 != null
                ) {
            return;
        }

        privilegedTemplatesEE5 = new ArrayList<String>();
        privilegedTemplates = new ArrayList<String>();

        for (WebPrivilegedTemplates webPrivililegedTemplates : Lookups.forPath(WEBTEMPLATE_PATH).lookupAll(WebPrivilegedTemplates.class)) {
            String[] addedTemplates = webPrivililegedTemplates.getPrivilegedTemplates(apiWebModule);
            if (addedTemplates != null && addedTemplates.length > 0){
                List<String> addedList = Arrays.asList(addedTemplates);
                privilegedTemplatesEE5.addAll(addedList);
                privilegedTemplates.addAll(addedList);
            }
        }

        privilegedTemplatesEE5.addAll(Arrays.asList(PRIVILEGED_NAMES_EE5));
        privilegedTemplates.addAll(Arrays.asList(PRIVILEGED_NAMES));
    }

    private final class RecommendedTemplatesImpl implements RecommendedTemplates,
            PrivilegedTemplates, PropertyChangeListener {
        private WebProject project;
        private J2eeProjectCapabilities projectCap;

        RecommendedTemplatesImpl (WebProject project) {
            this.project = project;
            project.evaluator().addPropertyChangeListener(this);
        }

        private boolean checked = false;
        private boolean isArchive = false;
        private boolean isEE5 = false;
        private boolean serverSupportsEJB31 = false;
        private boolean serverSupportsEJB40 = false;

        @Override
        public String[] getRecommendedTypes() {
            checkEnvironment();
            if (isArchive) {
                return TYPES_ARCHIVE;
            } else if (projectCap.isEjb31LiteSupported() || projectCap.isEjb40LiteSupported()) {
                Set<String> set = new HashSet<>(Arrays.asList(TYPES));
                if (projectCap.isEjb31Supported() || projectCap.isEjb40Supported() 
                        || serverSupportsEJB31 || serverSupportsEJB40) {
                    set.addAll(Arrays.asList(TYPES_EJB31));
                }

                if (projectCap.isEjb32LiteSupported() || projectCap.isEjb40LiteSupported()) {
                    set.addAll(Arrays.asList(TYPES_EJB32_LITE));
                } else {
                    set.addAll(Arrays.asList(TYPES_EJB31_LITE));
                }
                return set.toArray(new String[0]);
            } else {
                return TYPES;
            }
        }

        @Override
        public String[] getPrivilegedTemplates() {
            checkEnvironment();
            if (isArchive) {
                return PRIVILEGED_NAMES_ARCHIVE;
            } else {
                Set<String> set = new HashSet<String>();
                if (projectCap.isEjb31LiteSupported()) {
                    set.addAll(getPrivilegedTemplatesEE5());
                    if (projectCap.isEjb31Supported() || projectCap.isEjb40Supported() 
                            || serverSupportsEJB31 || serverSupportsEJB40) {
                        set.addAll(Arrays.asList(PRIVILEGED_NAMES_EE6_FULL));
                    }

                    if (projectCap.isEjb32LiteSupported() || projectCap.isEjb40LiteSupported()) {
                        set.addAll(Arrays.asList(PRIVILEGED_NAMES_EE7_WEB));
                    } else {
                        set.addAll(Arrays.asList(PRIVILEGED_NAMES_EE6_WEB));
                    }
                } else if (isEE5) {
                    set.addAll(getPrivilegedTemplatesEE5());
                } else {
                    set.addAll(WebProject.this.getPrivilegedTemplates());
                }
                return set.toArray(new String[set.size()]);
            }
        }

        private void checkEnvironment() {
            if (!checked) {
                final Object srcType = helper.getStandardPropertyEvaluator().
                        getProperty(WebProjectProperties.JAVA_SOURCE_BASED);
                if ("false".equals(srcType)) {
                    isArchive = true;
                }
                projectCap = J2eeProjectCapabilities.forProject(project);
                Profile profile = Profile.fromPropertiesString(eval.getProperty(WebProjectProperties.J2EE_PLATFORM));
                isEE5 = profile == Profile.JAVA_EE_5;
                serverSupportsEJB31 = ProjectUtil.getSupportedProfiles(project).contains(Profile.JAVA_EE_6_FULL) ||
                        ProjectUtil.getSupportedProfiles(project).contains(Profile.JAVA_EE_7_FULL) ||
                        ProjectUtil.getSupportedProfiles(project).contains(Profile.JAVA_EE_8_FULL) ||
                        ProjectUtil.getSupportedProfiles(project).contains(Profile.JAKARTA_EE_8_FULL);
                serverSupportsEJB40 = ProjectUtil.getSupportedProfiles(project).contains(Profile.JAKARTA_EE_9_FULL)
                        || ProjectUtil.getSupportedProfiles(project).contains(Profile.JAKARTA_EE_9_1_FULL)
                        || ProjectUtil.getSupportedProfiles(project).contains(Profile.JAKARTA_EE_10_FULL);
                checked = true;
            }
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (WebProjectProperties.J2EE_SERVER_INSTANCE.equals(evt.getPropertyName())) {
                checked = false;
            }
        }

    }

    private class DeployOnSaveSupportProxy implements DeployOnSaveSupport {

        public synchronized void addArtifactListener(ArtifactListener listener) {
            copyOnSaveSupport.addArtifactListener(listener);
            artifactSupport.addArtifactListener(listener);
        }

        public synchronized void removeArtifactListener(ArtifactListener listener) {
            copyOnSaveSupport.removeArtifactListener(listener);
            artifactSupport.removeArtifactListener(listener);
        }

        public boolean containsIdeArtifacts() {
            return DeployOnSaveUtils.containsIdeArtifacts(eval, updateHelper, "build.classes.dir");
        }

    }

    /**
     * This class handle copying of web resources to appropriate place in build
     * dir. User is not forced to perform redeploy on JSP change. This
     * class is also used in true Deploy On Save.
     *
     * Class should not request project lock from FS listener methods
     * (deadlock prone).
     */
    private class CopyOnSaveSupport extends FileChangeAdapter implements PropertyChangeListener, DeployOnSaveListener {

        private FileObject docBase = null;

        private String docBaseValue = null;

        private FileObject webInf = null;

        private String webInfValue = null;

        private File resources = null;

        private String buildWeb = null;
        private String buildClasses = null;

        private final List<ArtifactListener> listeners = new CopyOnWriteArrayList<ArtifactListener>();

        /** Creates a new instance of CopyOnSaveSupport */
        public CopyOnSaveSupport() {
            super();
        }

        public void addArtifactListener(ArtifactListener listener) {
            listeners.add(listener);
        }

        public void removeArtifactListener(ArtifactListener listener) {
            listeners.remove(listener);
        }

        private boolean isCopyOnSaveEnabled() {
            return Boolean.parseBoolean(WebProject.this.evaluator().getProperty(WebProjectProperties.J2EE_COPY_STATIC_FILES_ON_SAVE));
        }

        public void initialize() throws FileStateInvalidException {
            WebProject.this.evaluator().addPropertyChangeListener(this);

            if (!isCopyOnSaveEnabled()) {
                return;
            }

            docBase = getWebModule().getDocumentBase();
            docBaseValue = evaluator().getProperty(WebProjectProperties.WEB_DOCBASE_DIR);
            webInf = getWebModule().getWebInf();
            webInfValue = evaluator().getProperty(WebProjectProperties.WEBINF_DIR);
            if (resources != null) {
                FileUtil.removeFileChangeListener(this, resources);
            }
            resources = getWebModule().getResourceDirectory();
            buildWeb = evaluator().getProperty(WebProjectProperties.BUILD_WEB_DIR);
            buildClasses = evaluator().getProperty("build.classes.dir");

            if (docBase != null) {
                docBase.addRecursiveListener(this);
            }

            if (webInf != null && !FileUtil.isParentOf(docBase, webInf)) {
                webInf.addRecursiveListener(this);
            }

            if (resources != null) {
                FileUtil.addFileChangeListener(this, resources);
            }

            // Add deployed resources notification listener
            webModule.getConfigSupport().addDeployOnSaveListener(this);

            LOGGER.log(Level.FINE, "Web directory is {0}", docBaseValue);
            LOGGER.log(Level.FINE, "WEB-INF directory is {0}", webInfValue);
        }

        public void cleanup() throws FileStateInvalidException {
            if (docBase != null) {
                docBase.removeRecursiveListener(this);
            }
            if (webInf != null && !FileUtil.isParentOf(docBase, webInf)) {
                webInf.removeRecursiveListener(this);
            }
            if (resources != null) {
                FileUtil.removeFileChangeListener(this, resources);
                resources = null;
            }

            WebProject.this.evaluator().removePropertyChangeListener(this);

            webModule.getConfigSupport().removeDeployOnSaveListener(this);
        }

        public void propertyChange(PropertyChangeEvent evt) {
            if (WebProjectProperties.WEB_DOCBASE_DIR.equals(evt.getPropertyName())
                    || WebProjectProperties.WEBINF_DIR.equals(evt.getPropertyName())
                    || WebProjectProperties.J2EE_COPY_STATIC_FILES_ON_SAVE.equals(evt.getPropertyName())
                    || WebProjectProperties.RESOURCE_DIR.equals(evt.getPropertyName())) {
                try {
                    cleanup();
                    initialize();
                } catch (org.openide.filesystems.FileStateInvalidException e) {
                    LOGGER.log(Level.INFO, null, e);
                }
            } else if (WebProjectProperties.BUILD_WEB_DIR.equals(evt.getPropertyName())) {
                // TODO copy all files ?
                Object value = evt.getNewValue();
                buildWeb = value == null ? null : value.toString();
            }
        }

        private void checkPreprocessors(FileObject fileObject) {
            CssPreprocessors.getDefault().process(WebProject.this, fileObject);
        }

        private void checkPreprocessors(FileObject fileObject, String originalName, String originalExtension) {
            CssPreprocessors.getDefault().process(WebProject.this, fileObject, originalName, originalExtension);
        }

        @Override
        public void fileChanged(FileEvent fe) {
            checkPreprocessors(fe.getFile());
            try {
                if (!handleResource(fe)) {
                    handleCopyFileToDestDir(fe.getFile());
                }
            } catch (IOException e) {
                LOGGER.log(Level.INFO, null, e);
            }
        }

        @Override
        public void fileFolderCreated(FileEvent fe) {
            try {
                if (!handleResource(fe)) {
                    handleCopyFileToDestDir(fe.getFile());
                }
            } catch (IOException e) {
                LOGGER.log(Level.INFO, null, e);
            }
        }

        @Override
        public void fileDataCreated(FileEvent fe) {
            checkPreprocessors(fe.getFile());
            try {
                if (!handleResource(fe)) {
                    handleCopyFileToDestDir(fe.getFile());
                }
            } catch (IOException e) {
                LOGGER.log(Level.INFO, null, e);
            }
        }

        @Override
        public void fileRenamed(FileRenameEvent fe) {
            checkPreprocessors(fe.getFile(), fe.getName(), fe.getExt());
            try {
                if (handleResource(fe)) {
                    return;
                }

                FileObject fo = fe.getFile();
                handleCopyFileToDestDir(fo);

                FileObject persistenceXmlDir = getWebModule().getPersistenceXmlDir();
                if (persistenceXmlDir != null && FileUtil.isParentOf(persistenceXmlDir, fo)
                        && "persistence.xml".equals(fe.getName() + "." + fe.getExt())) { // NOI18N
                    String path = "WEB-INF/classes/META-INF/" + FileUtil.getRelativePath(persistenceXmlDir, fo.getParent())
                            + "/" + fe.getName() + "." + fe.getExt(); // NOI18N
                    if (!isSynchronizationAppropriate(path)) {
                        return;
                    }
                    handleDeleteFileInDestDir(path);
                    return;
                }

                FileObject webInf = getWebModule().resolveWebInf(docBaseValue, webInfValue, true, true);
                FileObject docBase = getWebModule().resolveDocumentBase(docBaseValue, false);

                if (webInf != null && FileUtil.isParentOf(webInf, fo)
                        && !(webInf.getParent() != null && webInf.getParent().equals(docBase))) {
                    // inside webinf
                    FileObject parent = fo.getParent();
                    String path;
                    if (FileUtil.isParentOf(webInf, parent)) {
                        path = FileUtil.getRelativePath(webInf, fo.getParent())
                                + "/" + fe.getName() + "." + fe.getExt(); // NOI18N
                    } else {
                        path = fe.getName() + "." + fe.getExt(); // NOI18N
                    }
                    path = "WEB-INF/" + path;

                    if (!isSynchronizationAppropriate(path))  {
                        return;
                    }
                    handleDeleteFileInDestDir(path);
                }

                if (docBase != null && FileUtil.isParentOf(docBase, fo)) {
                    // inside docbase
                    FileObject parent = fo.getParent();
                    String path;
                    if (FileUtil.isParentOf(docBase, parent)) {
                        path = FileUtil.getRelativePath(docBase, fo.getParent())
                                + "/" + fe.getName() + "." + fe.getExt(); // NOI18N
                    } else {
                        path = fe.getName() + "." + fe.getExt(); // NOI18N
                    }
                    if (!isSynchronizationAppropriate(path))  {
                        return;
                    }
                    handleDeleteFileInDestDir(path);
                }
            } catch (IOException e) {
                LOGGER.log(Level.INFO, null, e);
            }
        }

        @Override
        public void fileDeleted(FileEvent fe) {
            checkPreprocessors(fe.getFile());
            try {
                if (handleResource(fe)) {
                    return;
                }

                FileObject fo = fe.getFile();

                FileObject persistenceXmlDir = getWebModule().getPersistenceXmlDir();
                if (persistenceXmlDir != null && FileUtil.isParentOf(persistenceXmlDir, fo)
                        && "persistence.xml".equals(fo.getNameExt())) { // NOI18N
                    String path = "WEB-INF/classes/META-INF/" + FileUtil.getRelativePath(persistenceXmlDir, fo); // NOI18N
                    if (!isSynchronizationAppropriate(path)) {
                        return;
                    }
                    handleDeleteFileInDestDir(path);
                    return;
                }

                FileObject webInf = getWebModule().resolveWebInf(docBaseValue, webInfValue, true, true);
                FileObject docBase = getWebModule().resolveDocumentBase(docBaseValue, false);

                if (webInf != null && FileUtil.isParentOf(webInf, fo)
                        && !(webInf.getParent() != null && webInf.getParent().equals(docBase))) {
                    // inside webInf
                    String path = "WEB-INF/" + FileUtil.getRelativePath(webInf, fo); // NOI18N
                    if (!isSynchronizationAppropriate(path)) {
                        return;
                    }
                    handleDeleteFileInDestDir(path);
                }
                if (docBase != null && FileUtil.isParentOf(docBase, fo)) {
                    // inside docbase
                    String path = FileUtil.getRelativePath(docBase, fo);
                    if (!isSynchronizationAppropriate(path)) {
                        return;
                    }
                    handleDeleteFileInDestDir(path);
                }
            } catch (IOException e) {
                LOGGER.log(Level.INFO, null, e);
            }
        }

        private boolean isSynchronizationAppropriate(String filePath) {
            if (filePath.startsWith("WEB-INF/classes") && !filePath.startsWith("WEB-INF/classes/META-INF")) { // NOI18N
                return false;
            }
            if (filePath.startsWith("WEB-INF/src")) { // NOI18N
                return false;
            }
            if (filePath.startsWith("WEB-INF/lib")) { // NOI18N
                return false;
            }
            return true;
        }

        private void fireArtifactChange(Iterable<ArtifactListener.Artifact> artifacts) {
            for (ArtifactListener listener : listeners) {
                listener.artifactsUpdated(artifacts);
            }
        }

        private boolean handleResource(FileEvent fe) {
            // this may happen in broken project - see issue #191516
            // in any case it can't be resource event when resources is null
            if (resources == null) {
                return false;
            }
            FileObject resourceFo = FileUtil.toFileObject(resources);
            if (resourceFo != null
                    && (resourceFo.equals(fe.getFile()) || FileUtil.isParentOf(resourceFo, fe.getFile()))) {

                fireArtifactChange(Collections.singleton(
                        Artifact.forFile(FileUtil.toFile(fe.getFile())).serverResource()));
                return true;
            }
            return false;
        }

        private void handleDeleteFileInDestDir(String resourcePath) throws IOException {
            File deleted = null;
            FileObject webBuildBase = buildWeb == null ? null : helper.resolveFileObject(buildWeb);
            if (webBuildBase != null) {
                // project was built
                FileObject toDelete = webBuildBase.getFileObject(resourcePath);
                if (toDelete != null) {
                    deleted = FileUtil.toFile(toDelete);
                    toDelete.delete();
                }
                if (deleted != null) {
                    fireArtifactChange(Collections.singleton(ArtifactListener.Artifact.forFile(deleted)));
                }
            }
        }

        private void handleCopyFileToDestDir(FileObject fo) throws IOException {
            if (fo.isVirtual()) {
                return;
            }

            FileObject persistenceXmlDir = getWebModule().getPersistenceXmlDir();
            if (persistenceXmlDir != null && FileUtil.isParentOf(persistenceXmlDir, fo)
                    && "persistence.xml".equals(fo.getNameExt())) { // NOI18N
                handleCopyFileToDestDir("WEB-INF/classes/META-INF", persistenceXmlDir, fo); // NOI18N
                return;
            }

            FileObject webInf = getWebModule().resolveWebInf(docBaseValue, webInfValue, true, true);
            FileObject docBase = getWebModule().resolveDocumentBase(docBaseValue, false);

            if (webInf != null && FileUtil.isParentOf(webInf, fo)
                    && !(webInf.getParent() != null && webInf.getParent().equals(docBase))) {
                handleCopyFileToDestDir("WEB-INF", webInf, fo); // NOI18N
                return;
            }
            if (docBase != null && FileUtil.isParentOf(docBase, fo)) {
                handleCopyFileToDestDir(null, docBase, fo);
                return;
            }
        }

        private void handleCopyFileToDestDir(String prefix, FileObject baseDir, FileObject fo) throws IOException {
            if (fo.isVirtual()) {
                return;
            }

            if (baseDir != null && FileUtil.isParentOf(baseDir, fo)) {
                // inside docbase
                String path = FileUtil.getRelativePath(baseDir, fo);
                if (prefix != null) {
                    path = prefix + "/" + path;
                }
                if (!isSynchronizationAppropriate(path)) {
                    return;
                }

                FileObject webBuildBase = buildWeb == null ? null : helper.resolveFileObject(buildWeb);
                if (webBuildBase != null) {
                    // project was built
                    if (FileUtil.isParentOf(baseDir, webBuildBase) || FileUtil.isParentOf(webBuildBase, baseDir)) {
                        //cannot copy into self
                        return;
                    }
                    FileObject destFile = ensureDestinationFileExists(webBuildBase, path, fo.isFolder());
                    if (!fo.isFolder()) {
                        InputStream is = null;
                        OutputStream os = null;
                        FileLock fl = null;
                        try {
                            is = fo.getInputStream();
                            fl = destFile.lock();
                            os = destFile.getOutputStream(fl);
                            FileUtil.copy(is, os);
                        } finally {
                            if (is != null) {
                                is.close();
                            }
                            if (os != null) {
                                os.close();
                            }
                            if (fl != null) {
                                fl.releaseLock();
                            }
                            File file = FileUtil.toFile(destFile);
                            if (file != null) {
                                fireArtifactChange(Collections.singleton(ArtifactListener.Artifact.forFile(file)));
                            }
                        }
                    } else {
                        File file = FileUtil.toFile(destFile);
                        if (file != null) {
                            fireArtifactChange(Collections.singleton(ArtifactListener.Artifact.forFile(file)));
                        }
                    }
                }
            }
        }

        /**
         * Returns the destination (parent) directory needed to create file
         * with relative path path under webBuilBase
         */
        @NonNull
        private FileObject ensureDestinationFileExists(FileObject webBuildBase, String path, boolean isFolder) throws IOException {
            FileObject current = webBuildBase;
            StringTokenizer st = new StringTokenizer(path, "/");
            while (st.hasMoreTokens()) {
                String pathItem = st.nextToken();
                FileObject newCurrent = current.getFileObject(pathItem);
                if (newCurrent == null) {
                    // need to create it
                    if (isFolder || st.hasMoreTokens()) {
                        // create a folder
                        newCurrent = FileUtil.createFolder(current, pathItem);
                        assert newCurrent != null : "webBuildBase: " + webBuildBase + ", path: " + path + ", isFolder: " + isFolder;
                    } else {
                        newCurrent = FileUtil.createData(current, pathItem);
                        assert newCurrent != null : "webBuildBase: " + webBuildBase + ", path: " + path + ", isFolder: " + isFolder;
                    }
                }
                current = newCurrent;
            }
            assert current != null : "webBuildBase: " + webBuildBase + ", path: " + path + ", isFolder: " + isFolder;
            return current;
        }

        public void deployed(Iterable<Artifact> artifacts) {
            if (!easelSupport.canReload()) {
                return;
            }
            for (Artifact artifact : artifacts) {
                FileObject fileObject = getReloadFileObject(artifact);
                if (fileObject != null) {
                    easelSupport.reload(fileObject);
                }
            }
        }

        private FileObject getReloadFileObject(Artifact artifact) {
            File file = artifact.getFile();
            FileObject fileObject = FileUtil.toFileObject(FileUtil.normalizeFile(file));
            if (fileObject == null) {
                return null;
            }
            return findSourceForBinary(fileObject);
        }

        // "Binary" means compiled class file or web root document copied into build directory
        private FileObject findSourceForBinary(FileObject artifact) {
            FileObject webClassesBase = buildClasses == null ? null : helper.resolveFileObject(buildClasses);
            FileObject webBuildBase = buildWeb == null ? null : helper.resolveFileObject(buildWeb);

            if (webClassesBase != null && FileUtil.isParentOf(webClassesBase, artifact)) {
                String path = FileUtil.getRelativePath(webClassesBase, artifact).replace(".class", ".java"); // NOI18N
                for (SourceGroup sg : ProjectUtils.getSources(WebProject.this).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) {
                    FileObject fo = sg.getRootFolder().getFileObject(path);
                    if (fo != null) {
                        return fo;
                    }
                }
                return null;
            }

            if (docBase != null && webBuildBase != null) {
                if (FileUtil.isParentOf(webBuildBase, artifact)) {
                    String path = FileUtil.getRelativePath(webBuildBase, artifact);
                    return docBase.getFileObject(path);
                }
            }
            return null;
        }
    }

    private class ArtifactCopySupport extends ArtifactCopyOnSaveSupport {

        public ArtifactCopySupport() {
            super(WebProjectProperties.BUILD_WEB_DIR, evaluator(), getAntProjectHelper());
        }

        @Override
        public List<ArtifactCopyOnSaveSupport.Item> getArtifacts() {
            final AntProjectHelper helper = getAntProjectHelper();

            ClassPathSupport cs = new ClassPathSupport(evaluator(), getReferenceHelper(),
                    helper, getUpdateHelper(), new ClassPathSupportCallbackImpl(helper));

            List<ArtifactCopyOnSaveSupport.Item> result = new ArrayList<ArtifactCopyOnSaveSupport.Item>();
            for (ClassPathSupport.Item item : cs.itemsList(
                    helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH).getProperty(ProjectProperties.JAVAC_CLASSPATH),
                    WebProjectProperties.TAG_WEB_MODULE_LIBRARIES)) {

                if (!item.isBroken() && (item.getType() == ClassPathSupport.Item.TYPE_ARTIFACT
                        || item.getType() == ClassPathSupport.Item.TYPE_LIBRARY
                        || item.getType() == ClassPathSupport.Item.TYPE_JAR)) {
                    String path = item.getAdditionalProperty(ClassPathSupportCallbackImpl.PATH_IN_DEPLOYMENT);
                    String dirs = item.getAdditionalProperty(AntProjectConstants.DESTINATION_DIRECTORY);
                    if (path != null) {
                        result.add(new Item(item, new ItemDescription(path, RelocationType.fromString(dirs))));
                    }
                }
            }
            return result;
        }

        @Override
        protected Artifact filterArtifact(Artifact artifact, RelocationType type) {
            if (containsTLD(artifact.getFile()) || type == RelocationType.NONE) {
                return artifact;
            }
            if (type == RelocationType.ROOT) {
                return artifact.relocatable();
            } else if (type == RelocationType.LIB) {
                return artifact.relocatable("lib"); // NOI18N
            } else {
                return artifact;
            }
        }

        private boolean containsTLD(File f) {
            if (f.exists() && f.isFile() && f.canRead()) {
                ZipFile zip = null;
                try {
                    zip = new ZipFile(f);
                    for (Enumeration entries = zip.entries(); entries.hasMoreElements();) {
                        String zipEntryName = ((ZipEntry) entries.nextElement()).getName();
                        if (TLD_PATTERN.matcher(zipEntryName).matches()) {
                            return true;
                        }
                    }
                    return false;
                } catch (IOException ex) {
                    LOGGER.log(Level.INFO, null, ex);
                } finally {
                    if (zip != null) {
                        try {
                            zip.close();
                        } catch (IOException ex) {
                            LOGGER.log(Level.INFO, null, ex);
                        }
                    }
                }
            }

            return false;
        }
    }

    public boolean isJavaEE5(Project project) {
        return Profile.JAVA_EE_5.equals(getAPIWebModule().getJ2eeProfile());
    }

    private static final class WebPropertyEvaluatorImpl implements WebPropertyEvaluator {
        private PropertyEvaluator evaluator;
        public WebPropertyEvaluatorImpl (PropertyEvaluator eval) {
            evaluator = eval;
        }
        public PropertyEvaluator evaluator() {
            return evaluator;
        }
    }

    private class WebExtenderImplementation implements AntBuildExtenderImplementation {
        //add targets here as required by the external plugins..
        public List<String> getExtensibleTargets() {
            String[] targets = new String[] {
                "-do-init", "-init-check", "-post-clean", "jar", "-pre-pre-compile","-do-ws-compile","-do-compile","-do-compile-single", "-post-compile", "-pre-dist", //NOI18N
            };
            return Arrays.asList(targets);
        }

        public Project getOwningProject() {
            return WebProject.this;
        }

    }

    private static final class CoSAwareFileBuiltQueryImpl implements FileBuiltQueryImplementation, PropertyChangeListener
    {

        private final FileBuiltQueryImplementation delegate;
        private final WebProject project;
        private final AtomicBoolean cosEnabled = new AtomicBoolean();
        private final Map<FileObject, Reference<StatusImpl>> file2Status = new WeakHashMap<FileObject, Reference<StatusImpl>>();

        public CoSAwareFileBuiltQueryImpl(FileBuiltQueryImplementation delegate, WebProject project)
        {

            this.delegate = delegate;
            this.project = project;
            project.evaluator().addPropertyChangeListener(this);
            setCoSEnabledAndXor();

        }

        private synchronized StatusImpl readFromCache(FileObject file)
        {
            Reference<StatusImpl> r = file2Status.get(file);
            return r != null ? r.get() : null;

        }

        public Status getStatus(FileObject file)
        {
            StatusImpl result = readFromCache(file);
            if (result != null)
            {
                return result;
            }


            Status status = delegate.getStatus(file);
            if (status == null)
            {
                return null;
            }

            synchronized (this)
            {
                StatusImpl foisted = readFromCache(file);
                if (foisted != null)
                {
                    return foisted;
                }

                file2Status.put(file, new WeakReference<StatusImpl>(result = new StatusImpl(cosEnabled, status)));
            }

            return result;

        }

        boolean setCoSEnabledAndXor()
        {
            boolean nue = Boolean.parseBoolean(project.evaluator().getProperty(
                                     WebProjectProperties.J2EE_COMPILE_ON_SAVE));
            boolean old = cosEnabled.getAndSet(nue);

            return old != nue;

        }

        public void propertyChange(PropertyChangeEvent evt)
        {
            if (!setCoSEnabledAndXor())
            {
                return;
            }

            Collection<Reference<StatusImpl>> toRefresh;

            synchronized (this)
            {
                toRefresh = new LinkedList<Reference<StatusImpl>>(file2Status.values());
            }

            for (Reference<StatusImpl> r : toRefresh)
            {
                StatusImpl s = r.get();

                if (s != null)
                {
                    s.stateChanged(null);
                }
            }
        }

        private static final class StatusImpl implements Status, ChangeListener
        {

            private final ChangeSupport cs = new ChangeSupport(this);
            private final AtomicBoolean cosEnabled;
            private final Status delegate;

            public StatusImpl(AtomicBoolean cosEnabled, Status delegate)
            {
                this.cosEnabled = cosEnabled;
                this.delegate = delegate;
                this.delegate.addChangeListener(this);
            }

            public boolean isBuilt()
            {
                return cosEnabled.get() || delegate.isBuilt();
            }

            public void addChangeListener(ChangeListener l)
            {
                cs.addChangeListener(l);
            }

            public void removeChangeListener(ChangeListener l)
            {
                cs.removeChangeListener(l);
            }

            public void stateChanged(ChangeEvent e)
            {

                cs.fireChange();

            }
        }
    }

    // FIXME this is just fallback for code searching for the old SPI in lookup
    // remove in next release
    @SuppressWarnings("deprecation")
    private static class WebModuleImpl implements WebModuleImplementation {

        private final WebModule apiModule;

        public WebModuleImpl(WebModule apiModule) {
            this.apiModule = apiModule;
        }

        public FileObject getWebInf() {
            return apiModule.getWebInf();
        }

        public MetadataModel<WebAppMetadata> getMetadataModel() {
            return apiModule.getMetadataModel();
        }

        public FileObject[] getJavaSources() {
            return apiModule.getJavaSources();
        }

        public String getJ2eePlatformVersion() {
            return apiModule.getJ2eePlatformVersion();
        }

        public FileObject getDocumentBase() {
            return apiModule.getDocumentBase();
        }

        public FileObject getDeploymentDescriptor() {
            return apiModule.getDeploymentDescriptor();
        }

        public String getContextPath() {
            return apiModule.getContextPath();
        }
    }

    private static class WebModuleImpl2 implements WebModuleImplementation2 {

        private final ProjectWebModule webModule;

        public WebModuleImpl2(ProjectWebModule webModule) {
            this.webModule = webModule;
        }

        public String getContextPath() {
            return webModule.getContextPath();
        }

        public FileObject getDeploymentDescriptor() {
            return webModule.getDeploymentDescriptor();
        }

        public FileObject getDocumentBase() {
            return webModule.getDocumentBase();
        }

        public Profile getJ2eeProfile() {
            return webModule.getJ2eeProfile();
        }

        public FileObject[] getJavaSources() {
            return webModule.getJavaSources();
        }

        public MetadataModel<WebAppMetadata> getMetadataModel() {
            return webModule.getMetadataModel();
        }

        public FileObject getWebInf() {
            return webModule.getWebInf();
        }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            webModule.addPropertyChangeListener(listener);
        }

        public void removePropertyChangeListener(PropertyChangeListener listener) {
            webModule.removePropertyChangeListener(listener);
        }

    }

    private static class WebProjectLookup extends ProxyLookup implements PropertyChangeListener{
        private Lookup base, ee6;
        private WebProject project;
        private ProjectBrowserProvider browserProvider;

        public WebProjectLookup(WebProject project, Lookup base, Lookup ee6, ProjectBrowserProvider browserProvider) {
            super(base);
            this.project = project;
            this.base = base;
            this.ee6 = ee6;
            this.browserProvider = browserProvider;
            updateLookup();
        }

        private void updateLookup(){
            List<Lookup> lookups = new ArrayList<>();
            lookups.add(base);
            Profile profile = Profile.fromPropertiesString(project.evaluator().getProperty(WebProjectProperties.J2EE_PLATFORM));
            if (Profile.JAVA_EE_6_FULL.equals(profile) || Profile.JAVA_EE_6_WEB.equals(profile)
                    || Profile.JAVA_EE_7_FULL.equals(profile) || Profile.JAVA_EE_7_WEB.equals(profile)
                    || Profile.JAVA_EE_8_FULL.equals(profile) || Profile.JAVA_EE_8_WEB.equals(profile)
                    || Profile.JAKARTA_EE_8_FULL.equals(profile) || Profile.JAKARTA_EE_8_WEB.equals(profile)
                    || Profile.JAKARTA_EE_9_FULL.equals(profile) || Profile.JAKARTA_EE_9_WEB.equals(profile)
                    || Profile.JAKARTA_EE_9_1_FULL.equals(profile) || Profile.JAKARTA_EE_9_1_WEB.equals(profile)
                    || Profile.JAKARTA_EE_10_FULL.equals(profile) || Profile.JAKARTA_EE_10_WEB.equals(profile)) {
                lookups.add(ee6);
            }
            if ("true".equals(project.evaluator().getProperty(WebProjectProperties.DISPLAY_BROWSER))) {
                lookups.add(Lookups.singleton(browserProvider));
            }
            setLookups(lookups.toArray(new Lookup[lookups.size()]));
        }

        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getPropertyName().equals(WebProjectProperties.J2EE_PLATFORM) ||
                evt.getPropertyName().equals(WebProjectProperties.DISPLAY_BROWSER) ){
                updateLookup();
            }
        }
    }

    private static class EnterpriseBeansListener implements PropertyChangeListener{
        private static final RequestProcessor rp = new RequestProcessor();
        private Task upgradeTask = null;
        private WebProject project;

        public EnterpriseBeansListener(WebProject project) {
            this.project = project;
        }

        public synchronized void propertyChange(final PropertyChangeEvent evt) {
            if (upgradeTask != null){
                upgradeTask.schedule(100);
                return;
            }
            upgradeTask = rp.post(new Runnable() {
                public void run() {
                    WebProjectUtilities.upgradeJ2EEProfile(project);
                }
            }, 100);
        }
    }

    private static final class ProjectWebRootProviderImpl implements ProjectWebRootProvider {

        private final WebProject project;


        private ProjectWebRootProviderImpl(WebProject project) {
            assert project != null;
            this.project = project;
        }

        @Override
        public FileObject getWebRoot(FileObject file) {
            WebModule webModule = WebModule.getWebModule(file);
            FileObject webRoot = webModule != null ? webModule.getDocumentBase() : null;
            if(webRoot != null) {
                //#181480 - the WebModule.getWebModule() returns a webmodule instance
                //also for files outside of document base, which is OK.
                return FileUtil.isParentOf(webRoot, file) ?
                    webRoot :
                    null;
            }
            return null;
        }

        @Override
        public Collection<FileObject> getWebRoots() {
            WebModule webModule = project.getAPIWebModule();
            FileObject webRoot = webModule != null ? webModule.getDocumentBase() : null;
            if (webRoot != null) {
                return Collections.singleton(webRoot);
            }
            return Collections.emptyList();
        }

    }

    private final class WhiteListUpdaterImpl extends WhiteListUpdater {

        public WhiteListUpdaterImpl(Project project) {
            super(project);
        }

        @Override
        public void addSettingListener() {
            evaluator().addPropertyChangeListener(new PropertyChangeListener() {

                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (evt.getPropertyName().equals(JavaEEProjectSettingConstants.J2EE_SERVER_INSTANCE)){
                        checkWhiteLists();
                    }
                    if (evt.getPropertyName().equals(ProjectProperties.JAVAC_CLASSPATH)){
                        // if classpath changes refresh whitelists as well:
                        updateWhitelist(null, getServerWhiteList());
                    }
                }
            });
        }
    }

    private class JavaEEProjectSettingsImpl implements JavaEEProjectSettingsImplementation {

        private final WebProject project;


        public JavaEEProjectSettingsImpl(WebProject project) {
            this.project = project;

            evaluator().addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (WebProjectProperties.SELECTED_BROWSER.equals(evt.getPropertyName())) {
                        easelSupport.resetBrowserSupport();
                    }
                }
            });
        }

        @Override
        public void setProfile(Profile profile) {
            setInSharedProperties(JavaEEProjectSettingConstants.J2EE_PLATFORM, profile.toPropertiesString());
        }

        @Override
        public Profile getProfile() {
            return webModule.getJ2eeProfile();
        }

        @Override
        public void setBrowserID(String browserID) {
            setInSharedProperties(JavaEEProjectSettingConstants.SELECTED_BROWSER, browserID);
        }

        @Override
        public String getBrowserID() {
            return evaluator().getProperty(JavaEEProjectSettingConstants.SELECTED_BROWSER);
        }

        @Override
        public void setServerInstanceID(String serverInstanceID) {
            setInSharedProperties(JavaEEProjectSettingConstants.J2EE_SERVER_INSTANCE, serverInstanceID);
        }

        @Override
        public String getServerInstanceID() {
            return evaluator().getProperty(JavaEEProjectSettingConstants.J2EE_SERVER_INSTANCE);
        }

        private void setInSharedProperties(String key, String value) {
            try {
                UpdateHelper helper = project.getUpdateHelper();
                EditableProperties projectProperties = helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
                projectProperties.setProperty(key, value);
                helper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, projectProperties);
                ProjectManager.getDefault().saveProject(project);
            } catch (IOException ex) {
                LOGGER.log(Level.WARNING, "Project properties couldn't be saved.", ex);
            }
        }
    }
}
