/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal.ide.model;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.IResourceActionFilter;
import org.eclipse.ui.actions.SimpleWildcardTester;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.model.WorkbenchAdapter;

/**
 * An IWorkbenchAdapter that represents IResources.
 */
public abstract class WorkbenchResource extends WorkbenchAdapter
	implements IResourceActionFilter
{
	
/**
 * An attribute used to store the last modified time
 * so we may determine if the other XML based attributes
 * found in IResourceActionFilter(XML_DTD_NAME and XML_FIRST_TAG)
 * are stale and, therefore, the xml file must be reparsed for this
 * information.
 */
public static final String XML_LAST_MOD = "xmlLastMod";	 //$NON-NLS-1$

/**
 *	Answer the appropriate base image to use for the resource.
 */
protected abstract ImageDescriptor getBaseImage(IResource resource);
/**
 * Returns an image descriptor for this object.
 */
public ImageDescriptor getImageDescriptor(Object o) {
	IResource resource = getResource(o);
	return resource == null ? null : getBaseImage(resource);
}
/**
 * getLabel method comment.
 */
public String getLabel(Object o) {
	IResource resource = getResource(o);
	return resource == null ? null : resource.getName();
}
/**
 * Returns the parent of the given object.  Returns null if the
 * parent is not available.
 */
public Object getParent(Object o) {
	IResource resource = getResource(o);
	return resource == null ? null : resource.getParent();
}
/**
 * Returns the resource corresponding to this object,
 * or null if there is none.
 */
protected IResource getResource(Object o) {
	if (o instanceof IResource) {
		return (IResource)o;
	}
	if (o instanceof IAdaptable) {
		return (IResource)((IAdaptable)o).getAdapter(IResource.class);
	}
	return null;
}
/**
 * Returns whether the specific attribute matches the state of the target
 * object.
 *
 * @param target the target object
 * @param name the attribute name
 * @param value the attribute value
 * @return <code>true</code> if the attribute matches; <code>false</code> otherwise
 */
public boolean testAttribute(Object target, String name, String value) {
	if (!(target instanceof IResource)) {
		return false;
	}
	IResource res = (IResource) target;
	if (name.equals(NAME)) {
		return SimpleWildcardTester.testWildcardIgnoreCase(value, 
			res.getName());
	} else if (name.equals(PATH)) {
		return SimpleWildcardTester.testWildcardIgnoreCase(value, 
			res.getFullPath().toString());
	} else if (name.equals(EXTENSION)) {
		return SimpleWildcardTester.testWildcardIgnoreCase(value, 
			res.getFileExtension());
	} else if (name.equals(READ_ONLY)) {
		return (res.isReadOnly() == value.equalsIgnoreCase("true"));//$NON-NLS-1$
	} else if (name.equals(PROJECT_NATURE)) {
		try {
			IProject proj = res.getProject();
			return proj.isAccessible() && proj.hasNature(value);
		} catch (CoreException e) {
			return false;		
		}
	} else if (name.equals(PERSISTENT_PROPERTY)) {
		return testProperty(res, true, false, value);
	} else if (name.equals(PROJECT_PERSISTENT_PROPERTY)) {
		return testProperty(res, true, true, value);
	} else if (name.equals(SESSION_PROPERTY)) {
		return testProperty(res, false, false, value);
	} else if (name.equals(PROJECT_SESSION_PROPERTY)) {
		return testProperty(res, false, true, value);
	} else if (name.equals(XML_FIRST_TAG)) {
		return testXMLProperty(res, name, value);
	} else if (name.equals(XML_DTD_NAME)) {
		return testXMLProperty(res, name, value);
	} else if (name.equals(CONTENT_TYPE_ID)) {
	    return testContentTypeProperty(res, value);
	}
	return false;
}

/**
 * Tests whether the content type for <code>resource</code> matches the
 * <code>contentTypeId</code>. It is possible that this method call could
 * cause the resource to be read. It is also possible (through poor plug-in
 * design) for this method to load plug-ins.
 * 
 * @param resource
 *            The resource for which the content type should be determined;
 *            must not be <code>null</code>.
 * @param contentTypeId
 *            The expected content type; must not be <code>null</code>.
 * @return <code>true</code> iff the best matching content type has an
 *         identifier that matches <code>contentTypeId</code>;
 *         <code>false</code> otherwise.
 */
private final boolean testContentTypeProperty(final IResource resource,
        final String contentTypeId) {
    final String expectedValue = contentTypeId.trim();

    if (!(resource instanceof IFile)) { return false; }

    final IFile file = (IFile) resource;
    String actualValue = null;

    try {
        final IContentDescription contentDescription = file
                .getContentDescription();

        if (contentDescription != null) {
            final IContentType contentType = contentDescription
                    .getContentType();
            actualValue = contentType.getId();
        }
    } catch (final CoreException e) {
        // Log the error.
        final String message = "Core exception while retrieving the content description"; //$NON-NLS-1$
        IDEWorkbenchPlugin.log(message,
                new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH,
                        IStatus.ERROR, message, e));
    }

    return expectedValue == null || expectedValue.equals(actualValue);
}

/**
 * Tests whether a session or persistent property on the resource or its project
 * matches the given value.
 * 
 * @param resource
 *            the resource to check
 * @param persistentFlag
 *            <code>true</code> for a persistent property, <code>false</code>
 *            for a session property
 * @param projectFlag
 *            <code>true</code> to check the resource's project,
 *            <code>false</code> to check the resource itself
 * @param value
 *            the attribute value, which has either the form "propertyName" or
 *            "propertyName=propertyValue"
 * @return whether there is a match
 */
private boolean testProperty(IResource resource, boolean persistentFlag, boolean projectFlag, String value) {
	String propertyName;
	String expectedVal;
	int i = value.indexOf('=');
	if (i != -1) {
		propertyName = value.substring(0, i).trim();
		expectedVal = value.substring(i+1).trim();
	}
	else {
		propertyName = value.trim();
		expectedVal = null;
	}
	try {
		QualifiedName key;
		int dot = propertyName.lastIndexOf('.');
		if (dot != -1) {
			key = new QualifiedName(propertyName.substring(0, dot), propertyName.substring(dot+1));
		}
		else {
			key = new QualifiedName(null, propertyName);
		}
		IResource resToCheck = projectFlag ? resource.getProject() : resource;
		 // getProject() on workspace root can be null
		if (resToCheck == null) {
			return false;
		}
		if (persistentFlag) {
			String actualVal = resToCheck.getPersistentProperty(key);
			if (actualVal == null) {
				return false;
			}
			return expectedVal == null || expectedVal.equals(actualVal);
		}
		else {
			Object actualVal = resToCheck.getSessionProperty(key);
			if (actualVal == null) {
				return false;
			}
			return expectedVal == null || expectedVal.equals(actualVal.toString());
		}
	} catch (CoreException e) {
		// ignore
	}
	return false;		
}

/**
 * Test whether a given xml property matches that xml
 * element in the file.  Note that these properties will
 * be stored as persistent properties.  If the underlying 
 * xml file changes, the xml will be reparsed to re-retrieve
 * these property values.
 * 
 * @param resource the resource associated with the xml file
 * @param propertyName the name of the property we are looking for
 * @param value the value we expect to find
 * @return true if the value found for this property, matches
 *     the value passed in as a parameter.
 * 
 * @deprecated This method will be removed in future builds.  It has been
 * replaced with testContentTypeProperty.
 */
private boolean testXMLProperty(IResource resource, String propertyName, String value) {
	String expectedVal = value.trim();
	try {
		QualifiedName key = new QualifiedName(IDEWorkbenchPlugin.IDE_WORKBENCH, propertyName);
		IResource resToCheck = resource;
		if (resToCheck == null) {
			return false;
		}
		// Check to see if the persistent properties are stale
		long modTime = resToCheck.getModificationStamp();
		QualifiedName modKey = new QualifiedName(IDEWorkbenchPlugin.IDE_WORKBENCH, XML_LAST_MOD);
		String lastPropMod = resToCheck.getPersistentProperty(modKey);
		long realLastPropMod = 0L;
		if (lastPropMod != null) {
			try {
				realLastPropMod = new Long(lastPropMod).longValue();
			} catch (NumberFormatException nfe) {
				// log it but continue working
				IDEWorkbenchPlugin.log("Problem converting last mod to long in testXMLProperty", new Status(IStatus.ERROR,IDEWorkbenchPlugin.IDE_WORKBENCH, IStatus.ERROR, "Problem converting last mod to long in testXMLProperty", nfe)); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
		String actualVal = null;
		if (modTime != IResource.NULL_STAMP && realLastPropMod == modTime) {
			// Make sure we don't pick up stale information
			actualVal = resToCheck.getPersistentProperty(key);
		} else if (realLastPropMod > 0l){
			// Make sure that these persistent properties
			// are cleared so that we don't pick up any
			// stale values by mistake.  If we've never parsed
			// this file, however, we don't need to worry
			// about stale values.
			QualifiedName qname1 = new QualifiedName(IDEWorkbenchPlugin.IDE_WORKBENCH, IResourceActionFilter.XML_DTD_NAME);
			QualifiedName qname2 = new QualifiedName(IDEWorkbenchPlugin.IDE_WORKBENCH, IResourceActionFilter.XML_FIRST_TAG);
			try {
				resToCheck.setPersistentProperty(qname1, null);
				resToCheck.setPersistentProperty(qname2, null);
			} catch (CoreException c) {
				IDEWorkbenchPlugin.log("Problem clearing stale xml properties", c.getStatus()); //$NON-NLS-1$
			}
		}
		
		// Either we have never parsed this file or we 
		// have parsed it but the file has changed since
		// the last time it was parsed.
		if (actualVal == null) {
			try {
				new PropertyParser().parseResource(resToCheck);
			} catch (Exception e) {
				IDEWorkbenchPlugin.log("Problem parsing for xml properties", new Status(IStatus.ERROR,IDEWorkbenchPlugin.IDE_WORKBENCH, IStatus.ERROR, "Problem parsing for xml properties", e)); //$NON-NLS-1$ //$NON-NLS-2$
			}
			// Now recheck the persistent property as it may have
			// been populated.
			actualVal = resToCheck.getPersistentProperty(key);
			if (actualVal == null)
				return false;
		}
		return expectedVal == null || expectedVal.equals(actualVal);
	} catch (CoreException e) {
		// Just output a message to the log file and continue
		IDEWorkbenchPlugin.log("Problem testing xml property", e.getStatus()); //$NON-NLS-1$
	}
	return false;		
}

}
