Skip to main content
summaryrefslogblamecommitdiffstats
blob: f44eada0a17e58f6c3a334ccf043ebf3d3bf0863 (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                                                                 
                                                   








                                            


                                                  
                                                  



                                                                                    
                                                





                                                                               
                                                         
                                           




































































































                                                                                                                                                                                                                                                                                                                                                                                      
                                                  

























                                                                                                                                            
                                                                                        












                                                                                                 
                                                                                            














                                                                                       
                                                               
                                                            
                                                                                    




















                                                                                 
                                                                 



















































                                                                                                                                        
























                                                                                                        









































































































                                                                                                                                                                                      




                                                                                  
                                                 
                                                               












































































                                                                                                                                                                                
                                                                                                                                                                                     





























                                                                                                                                                      








                                                                                      








































                                                                                   
                                            































                                                                                                   
                                                               
                                                            
                                                                                    








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

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jst.jsp.core.PageDirectiveAdapter;
import org.eclipse.jst.jsp.core.internal.text.rules.StructuredTextPartitionerForJSP;
import org.eclipse.jst.jsp.core.modelhandler.EmbeddedTypeStateData;
import org.eclipse.wst.sse.core.IAdapterFactory;
import org.eclipse.wst.sse.core.INodeAdapter;
import org.eclipse.wst.sse.core.INodeNotifier;
import org.eclipse.wst.sse.core.IStructuredModel;
import org.eclipse.wst.sse.core.internal.modelhandler.EmbeddedTypeRegistry;
import org.eclipse.wst.sse.core.internal.modelhandler.EmbeddedTypeRegistryImpl;
import org.eclipse.wst.sse.core.modelhandler.EmbeddedTypeHandler;
import org.eclipse.wst.sse.core.text.IStructuredDocument;
import org.eclipse.wst.sse.core.util.Debug;
import org.eclipse.wst.sse.core.util.StringUtils;
import org.eclipse.wst.xml.core.document.XMLNode;

/**
 * This class has the responsibility to provide 
 * an embedded factory registry for JSP Aware INodeAdapter Factories
 * to use. 
 * 
 * Typically, the embedded type is to be considered a feature of 
 * the document, so JSP Aware AdpaterFactories should call
 * getAdapter(PageDirectiveAdapter.class) directoy on the document
 * (or owning document) node.
 */
public class PageDirectiveAdapterImpl implements PageDirectiveAdapter {

	protected static final String STR_CHARSET = "charset"; //$NON-NLS-1$
	private final static Object adapterType = PageDirectiveAdapter.class;
	private IStructuredModel model;
	protected final String[] JAVASCRIPT_LANGUAGE_KEYS = new String[]{"javascript", "javascript1.0", "javascript1.1_3", "javascript1.2", "javascript1.3", "javascript1.4", "javascript1.5", "javascript1.6", "jscript", "sashscript"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$
	protected final String[] JAVA_LANGUAGE_KEYS = new String[]{"java"}; //$NON-NLS-1$

	/**
	 * Constructor for PageDirectiveAdapterImpl.
	 */
	public PageDirectiveAdapterImpl(INodeNotifier target) {
		super();
		notifierAtCreation = target;
		// we need to remember our instance of model, 
		// in case we need to "signal" a re-init needed.
		if (target instanceof XMLNode) {
			XMLNode node = (XMLNode) target;
			model = node.getModel();
		}

	}

	/**
	 * parses the full contentType value into its two parts
	 * the contentType, and the charset, if present. Note: this 
	 * method is a lightly modified version of a method in AbstractHeadParser.
	 * There, we're mostly interested in the charset part of contentTypeValue. 
	 * Here, we're mostly interested in the mimeType part.
	 */
	private String getMimeTypeFromContentTypeValue(String contentTypeValue) {
		if (contentTypeValue == null)
			return null;
		String cleanContentTypeValue = StringUtils.stripNonLetterDigits(contentTypeValue);
		StringTokenizer tokenizer = new StringTokenizer(cleanContentTypeValue, ";= \t\n\r\f"); //$NON-NLS-1$
		int tLen = tokenizer.countTokens();
		// if contains encoding should have three tokens, the mimetype, the word 'charset', and the encoding value
		String[] tokens = new String[tLen];
		int j = 0;
		while (tokenizer.hasMoreTokens()) {
			tokens[j] = tokenizer.nextToken();
			j++;
		}
		// 
		// Following is the common form for target expression		
		// <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
		// But apparrently is also valid without the content type there, 
		// just the charset, as follows:
		// <META http-equiv="Content-Type" content="charset=UTF-8">
		// So we'll loop through tokens and key off of 'charset'

		int charsetPos = -1;
		for (int i = 0; i < tokens.length; i++) {
			if (tokens[i].equalsIgnoreCase(STR_CHARSET)) {
				charsetPos = i;
				break;
			}
		}
		//String charset = null;
		String contentType = null;
		if (charsetPos > -1) {
			// case where charset was present
			//			int charsetValuePos = charsetPos + 1;
			//			if (charsetValuePos < tokens.length) {
			//				charset = tokens[charsetValuePos];
			//			}
			int contentTypeValuePos = charsetPos - 1;
			if (contentTypeValuePos > -1) {
				contentType = tokens[contentTypeValuePos];
			}
		}
		else {
			// charset was not present, so if there's 
			// a value, we assume its the contentType value
			if (tokens.length > 0) {
				contentType = tokens[0];
			}
		}
		return contentType;
	}

	private EmbeddedTypeHandler embeddedTypeHandler;
	private List embeddedFactoryRegistry = new ArrayList();
	private String cachedLanguage;
	private String cachedContentType;
	private INodeNotifier notifierAtCreation;

	private int firstLanguagePosition = -1;
	private int firstContentTypePosition = -1;

	private boolean reinitializing;

	/*
	 * @see INodeAdapter#isAdapterForType(Object)
	 */
	public boolean isAdapterForType(Object type) {
		return (type == adapterType);
	}

	/*
	 * @see INodeAdapter#notifyChanged(INodeNotifier, int, Object, Object, Object, int)
	 */
	public void notifyChanged(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) {
	}

	public void setEmbeddedType(EmbeddedTypeHandler handler) {
		// if really the same handler, no need for further processing
		if (embeddedTypeHandler == handler) {
			return;
		}
		// then one exists, and the new one is truely different, so we need to 
		// release and remove current factories
		if (embeddedTypeHandler != null) {
			Iterator list = embeddedFactoryRegistry.iterator();
			while (list.hasNext()) {
				IAdapterFactory factory = (IAdapterFactory) list.next();
				factory.release();
			}

			embeddedFactoryRegistry.clear();
		}

		embeddedTypeHandler = handler;
		// when the handler is set, "transfer" its factories to our own list.
		// note: our own list may also be added to else where, such as on
		// "editor side".
		if (embeddedTypeHandler != null) {
			Iterator iterator = embeddedTypeHandler.getAdapterFactories().iterator();
			while (iterator.hasNext()) {
				IAdapterFactory factory = (IAdapterFactory) iterator.next();
				embeddedFactoryRegistry.add(factory);
			}
		}
	}

	/**
	 * @see PageDirectiveAdapter#adapt(INodeNotifier, Object)
	 */
	public INodeAdapter adapt(INodeNotifier notifier, Object type) {
		INodeAdapter result = null;
		// if embeddedContentType hasn't been set, 
		// then we can not adapt it.
		if (embeddedTypeHandler != null) {
			if (embeddedFactoryRegistry != null) {
				Iterator iterator = embeddedFactoryRegistry.iterator();
				IAdapterFactory factory = null;
				while (iterator.hasNext()) {
					factory = (IAdapterFactory) iterator.next();
					if (factory.isFactoryForType(type)) {
						result = factory.adapt(notifier);
						break;
					}
				}
			}
		}
		return result;

	}

	/**
	 * @see PageDirectiveAdapter#getEmbeddedType()
	 */
	public EmbeddedTypeHandler getEmbeddedType() {
		if (embeddedTypeHandler == null) {
			embeddedTypeHandler = getDefaultEmbeddedType();
		}
		return embeddedTypeHandler;
	}

	public void addEmbeddedFactory(IAdapterFactory factory) {
		// should we check if already exists in list?
		embeddedFactoryRegistry.add(factory);
	}

	//	/**
	//	 * Used by PageDirectiveWatchers to signal that some important attribute has changed, and 
	//	 * any cached values should be re-calcuated
	//	 */
	//	void changed() {
	//		// we won't actually check if change is needed, if the model state is already changing.
	//		if (!model.isReinitializationNeeded()) {
	//			// go through our list of page watcher adapters, and updates the attributes 
	//			// we're interested in, if and only if they are the earliest occurance in the resource
	//			String potentialContentType = null;
	//			String potentialLanguage = null;
	//			int contentTypePosition = -1;
	//			int languagePosition = -1;
	//			Iterator iterator = pageDirectiveWatchers.iterator();
	//			while (iterator.hasNext()) {
	//				PageDirectiveWatcher pdWatcher = (PageDirectiveWatcher) iterator.next();
	//				String contentType = pdWatcher.getContentType();
	//				String language = pdWatcher.getLanguage();
	//				int offset = pdWatcher.getOffset();
	//				if (potentialContentType == null || (hasValue(contentType) && (offset < contentTypePosition))) {
	//					potentialContentType = contentType;
	//					contentTypePosition = offset;
	//				}
	//			}
	//			// now we have the best candiates for cached values, let's see if they've really changed from 
	//			// what we had. If so, note we go through the setters so side effects can take place there.
	//			potentialContentType = getMimeTypeFromContentTypeValue(potentialContentType);
	//			if (potentialContentType == null || potentialContentType.length() == 0) {
	//				//potentialContentType = getDefaultContentType();
	//			} else {
	//				setCachedContentType(potentialContentType);
	//			}
	//
	//			if (potentialLanguage != null && hasValue(potentialLanguage)) {
	//				setCachedLanguage(potentialLanguage);
	//			}
	//		}
	//	}
	void changedContentType(int elementOffset, String newValue) {
		// only need to process if this new value is 
		// earlier in the file than our current value
		if (firstContentTypePosition == -1 || elementOffset <= firstContentTypePosition) {
			// dw_TODO: update embedded partitioner in JSP document partitioner
			// nsd_TODO: update embedded partitioner in JSP document partitioner

			// no need to change current value, if we're told some 
			// earlier value is null or blank (sounds like an error, anyway)
			if (hasValue(newValue)) {
				firstContentTypePosition = elementOffset;
				String potentialContentType = getMimeTypeFromContentTypeValue(newValue);
				// only do the set processing if different
				// from what it already is
				//	if (!potentialContentType.equalsIgnoreCase(cachedLanguage)) {
				setCachedContentType(potentialContentType);
				//	}
			}
		}
	}

	/**
	 * Used by PageDirectiveWatchers to signal that some important attribute has changed, and 
	 * any cached values should be re-calcuated
	 */
	void changedLanguage(int elementOffset, String newValue) {
		// only need to process if this new value is 
		// earlier in the file than our current value
		// has to be less than or equal to, in case our previous earliest one, 
		// is itself changing!
		if (firstLanguagePosition == -1 || elementOffset <= firstLanguagePosition) {

			// no need to change current value, if we're told some 
			// earlier value is null or blank (sounds like an error, anyway)
			if (hasValue(newValue)) {
				firstLanguagePosition = elementOffset;
				// only do the set processing if different
				// from what it already is
				if (!newValue.equalsIgnoreCase(cachedLanguage)) {
					setCachedLanguage(newValue);
				}
			}

			// dw_TODO: set language in document partitioner
			// nsd_TODO: set language in document partitioner
		}
	}

	/**
	 * Used by PageDirectiveWatchers to signal that some important attribute has changed, and 
	 * any cached values should be re-calcuated
	 */
	void changedPageEncoding(int elementOffset, String newValue) {

		// we don't currently track active value, since
		// just need during read and write (where its 
		// calculated. We will need in future, to 
		// acurately clone a model and to display 
		// "current encoding" to user in status bar.
	}

	/**
	 * Method hasValue.
	 * @param contentType
	 * @return boolean
	 */
	private boolean hasValue(String value) {
		if (value != null && value.length() > 0)
			return true;
		else
			return false;
	}

	/**
	 * Returns the cachedContentType.
	 * @return String
	 */
	public String getContentType() {
		if (cachedContentType == null) {
			cachedContentType = getDefaultContentType();
		}
		return cachedContentType;
	}

	/**
	 * Method getDefaultContentType.
	 * @return String
	 */
	private String getDefaultContentType() {
		return "text/html"; //$NON-NLS-1$
	}

	/**
	 * Returns the cachedLanguage.
	 * @return String
	 */
	public String getLanguage() {
		if (cachedLanguage == null)
			cachedLanguage = getDefaultLanguage();
		return cachedLanguage;
	}

	/**
	 * Method getDefaultLanguage.
	 * @return String
	 */
	private String getDefaultLanguage() {
		return "java"; //$NON-NLS-1$
	}

	/**
	 * Sets the cachedContentType.
	 * @param cachedContentType The cachedContentType to set
	 */
	public void setCachedContentType(String newContentType) {
		// if the passed in value is the same as existing, there's nothing to do.
		// if its different, then we need to change the contentHandler as well
		// and, more to the point, signal a re-initializtation is needed.
		// Note: if the value we're getting set to does not have a handler in the registry, 
		// we'll actually not set it to null or anything, we'll just continue on with the one
		// we have. This is pretty important to avoid re-initializing on every key stroke if someone 
		// is typing in a new content type, but haven't yet finished the whole "word".
		// However, if an contentType is not recognized, the registry returns the one 
		// for XML.
		//		if (this.cachedContentType != null && this.cachedContentType.equalsIgnoreCase(newContentType)) { // then do nothing
		//		} else {
		this.cachedContentType = newContentType;
		// see if we can update embedded handler
		//		if (this.cachedContentType == null || this.cachedContentType.length() == 0) { // do nothing, don't can't get a new handler, so we'll keep what we have
		//		} else {

		// getHandler should always return something (never null), based 
		// on the rules in the factory. 
		EmbeddedTypeHandler handler = getHandlerFor(this.cachedContentType);
		// we do this check for re-init here, instead of in setEmbeddedType, 
		// since setEmbeddedType is called during the normal initializtion 
		// process, when re-init is not needed (since there is no content)
		if (embeddedTypeHandler != null && handler != null && embeddedTypeHandler != handler) {
			// changing this embedded handler here may 
			// be in the middle of anotify loop, not sure
			// if that'll cause problems.
			
			// be sure to hold oldHandler in temp var
			// or else setEmbeddedType will "reset" it
			// before modelReinitNeeded(oldHandler, handler) is called
		    EmbeddedTypeHandler oldHandler = embeddedTypeHandler;
			setEmbeddedType(handler);
			modelReinitNeeded(oldHandler, handler);
		}
		//		}

		//		}

	}

	/**
	 * This method is used to re-init based on embeddedTypeHandler
	 * changing. It is given priority over the language change, since
	 * there its more important to have old and new handlers's in the 
	 * stateData field.
	 */
	private void modelReinitNeeded(EmbeddedTypeHandler oldHandler, EmbeddedTypeHandler newHandler) {
		if (model.isReinitializationNeeded()) {
			System.out.println("already being initialized"); //$NON-NLS-1$
		}

		try {
			model.aboutToChangeModel();
			model.setReinitializeStateData(new EmbeddedTypeStateData(oldHandler, newHandler));
			model.setReinitializeNeeded(true);
		}
		finally {
			model.changedModel();
		}
	}

	/**
	 * Method modelReinitNeeded.
	 */
	private void modelReinitNeeded(String oldlanguage, String newLanguage) {
		// bit of a short cut for now .... we dont' need language at the moment, 
		// but should set the state data
		if (model.isReinitializationNeeded()) {
			if (Debug.displayWarnings) {
				System.out.println("already being initialized"); //$NON-NLS-1$
			}
		}
		else {
			try {
				// if already being re-initialized, we don't want to 
				// reset the data in the stateData field.
				model.aboutToChangeModel();
				model.setReinitializeStateData(newLanguage);
				model.setReinitializeNeeded(true);
			}
			finally {
				model.changedModel();
			}
		}
	}

	public void setCachedLanguage(String newLanguage) {
		if (cachedLanguage != null && languageStateChanged(cachedLanguage, newLanguage)) { // a complete re-init overkill in current system, since really just need for 
			// the line style providers, 
			// BUT, a change in language could effect other things, 
			// and we don't expect to happen often so a little overkill isn't too bad.
			// The deep problem is that there is no way to get at the "edit side" adpapters
			// specifically here in model class.
			// we have to do the model changed sequence to get the 
			// screen to update.
			// do not signal again, if signaled once (the reinit state data will be wrong.
			// (this needs to be improved in future)
			if (!model.isReinitializationNeeded()) {
				modelReinitNeeded(cachedLanguage, newLanguage);
			}
		}
		setLanguage(newLanguage);
	}

	/**
	 * This is public access method, used especially
	 * from loader, for JSP Fragment support. 
	 */
	public void setLanguage(String newLanguage) {
		this.cachedLanguage = newLanguage;
		IDocumentPartitioner partitioner = ((IDocumentExtension3) model.getStructuredDocument()).getDocumentPartitioner(IStructuredDocument.DEFAULT_STRUCTURED_PARTITIONING);
		if (partitioner instanceof StructuredTextPartitionerForJSP) {
			((StructuredTextPartitionerForJSP) partitioner).setLanguage(newLanguage);
		}
	}

	/**
	 * Method languageStateChange.
	 * @param cachedLanguage
	 * @param newLanguage
	 * @return boolean
	 */
	private boolean languageStateChanged(String cachedLanguage, String newLanguage) {
		boolean result = false; // languages are equal, then no change in state
		if (!cachedLanguage.equalsIgnoreCase(newLanguage)) {
			boolean oldLanguageKnown = languageKnown(cachedLanguage);
			boolean newLanguageKnown = languageKnown(newLanguage);
			result = newLanguageKnown || (!newLanguageKnown && oldLanguageKnown);
		}
		return result;
	}

	/**
	 * Method languageKnown.
	 * @param cachedLanguage
	 * @return boolean
	 */
	private boolean languageKnown(String language) {
		return (StringUtils.contains(JAVA_LANGUAGE_KEYS, language, false) || StringUtils.contains(JAVASCRIPT_LANGUAGE_KEYS, language, false));
	}

	private IFile getFile(IStructuredModel model) {
		String location = model.getBaseLocation();
		IPath path = new Path(location);
		if (!path.toFile().exists() && path.segmentCount() > 1) {
			return ResourcesPlugin.getWorkspace().getRoot().getFile(path);
		}
		return null;
	}

	private EmbeddedTypeHandler getHandlerFor(String contentType) {
		EmbeddedTypeRegistry reg = getEmbeddedContentTypeRegistry();
		EmbeddedTypeHandler handler = null;
		if (reg != null)
			handler = reg.getTypeFor(contentType);
		return handler;
	}

	/**
	 * Gets the embeddedContentTypeRegistry.
	 * @return Returns a EmbeddedContentTypeRegistry
	 */
	private EmbeddedTypeRegistry getEmbeddedContentTypeRegistry() {
		return EmbeddedTypeRegistryImpl.getInstance();
	}

	/**
	 * For JSP files, text/html is the default 
	 * content type. This may want this different 
	 * for types like jsv (jsp for voice xml)
	 * For now, hard code to new instance.
	 * In future, should get instance from registry.
	 * 
	 * Specification cites HTML as the default contentType.
	 */
	protected EmbeddedTypeHandler getDefaultEmbeddedType() {
		return getHandlerFor(getDefaultContentType());
	}

	public void contentSettingsChanged(IResource resource) {
		// Note: we currently get notified multiple times, 
		// I assume since there's mulitple fields in the properties.
		// For now, I'll assume that once we get notified, all the 
		// fields are accurate, so if we're reinitializing, don't 
		// check any further. To NOT do this causes concurrent modification
		// exceptions. To do it, may cause us to miss when user changes
		// two fields at once. Will need to test. 
		if (reinitializing)
			return;
		if (resource == null)
			return;
		IFile file = getFile(model);
		if (file == null)
			return;
		//        String filename = null;
		//        if (resource.FILE == resource.getType()) {
		//        	filename = resource.getLocation().toString();
		//        }
		IProject project = file.getProject();
		if (project == null)
			return;
		if (!project.equals(resource.getProject()))
			return;
		// Note: these change notifications appear to be coming 
		// in based on any change in project. I'm not sure how
		// to tell if they are for my particular file, or 
		// if there's some other error I'm making in listeners.
		// the setters below should be smart enough 
		// to know if a meaningful change occurred, or 
		// not. Note: we seem to get called a lot (for resources other than our own?)
		// with lots of 'null' values. The logic below may prevent the correct unsetting
		// of a property (such as changing from a language setting back to 'none'). 
		// We may need a 'none' id, or something, to help detect that case, since we can't 
		// always assume the 'null' is accurate.
	}

	public INodeNotifier getTarget() {
		return notifierAtCreation;
	}

	public void release(Object type) {
		if (embeddedTypeHandler != null) {
			if (embeddedFactoryRegistry != null) {
				Iterator iterator = embeddedFactoryRegistry.iterator();
				IAdapterFactory factory = null;
				while (iterator.hasNext()) {
					factory = (IAdapterFactory) iterator.next();
					if (factory.isFactoryForType(type)) {
						factory.release();
					}
				}
			}
		}

	}
}

Back to the top