/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.editors.unified; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.jface.text.ITypedRegion; import com.aptana.ide.core.IdeLog; import com.aptana.ide.core.StringUtils; import com.aptana.ide.editors.UnifiedEditorsPlugin; import com.aptana.ide.editors.managers.FileContextManager; import com.aptana.ide.editors.unified.errors.IErrorManager; import com.aptana.ide.editors.unified.errors.IFileError; import com.aptana.ide.editors.unified.errors.IFileErrorListener; import com.aptana.ide.lexer.IRange; import com.aptana.ide.lexer.LexemeList; import com.aptana.ide.lexer.LexerException; import com.aptana.ide.parsing.ILanguageChangeListener; import com.aptana.ide.parsing.IParseState; import com.aptana.ide.parsing.IParser; /** * @author Robin Debreuil */ public class FileService implements IFileService { private Object _parserLock; private Map<String, IFileLanguageService> _languageServices; private IParseState _parseState; private IFileSourceProvider _connectedSourceProvider; private IFileSourceProvider _defaultSourceProvider; private IParser _parser; private IParser _scanner; private IPartitionScanner _partitionScanner; private List<IFileContextListener> _listeners; private List<IFileContextListener> _delayedListeners; private List<IFileContextListener> _longDelayedListeners; private String _defaultLanguage; private ILanguageChangeListener _languageChangeListener; private IErrorManager _errorManager; private IRedrawRangeListener _redrawListener; private IFileError[] _errorMarkers; private List<IFileErrorListener> _errorListeners; private ITypedRegion[] _partitions; /** * FileService * * @param parser * @param parseState * @param defaultSourceProvider * @param defaultLanguage */ public FileService(IParser parser, IParseState parseState, IFileSourceProvider defaultSourceProvider, String defaultLanguage) { this._parseState = parseState; this._defaultSourceProvider = defaultSourceProvider; this._defaultLanguage = defaultLanguage; this._parserLock = new Object(); this._languageServices = new HashMap<String, IFileLanguageService>(); this._listeners = new ArrayList<IFileContextListener>(); this._delayedListeners = new ArrayList<IFileContextListener>(); this._longDelayedListeners = new ArrayList<IFileContextListener>(); this._errorMarkers = new IFileError[0]; this._errorListeners = new ArrayList<IFileErrorListener>(); this.setParser(parser); // add delay listener for long parse this.addDelayedFileListener(new IFileContextListener() { /** * @see com.aptana.ide.editors.unified.IFileContextListener#onContentChanged(com.aptana.ide.editors.unified.FileContextContentEvent) */ public void onContentChanged(FileContextContentEvent evt) { if (FileService.this._scanner != null && FileService.this._parser != null) { synchronized (FileService.this) { synchronized (FileService.this._parserLock) { IParseState parseState = getParseState(); if (parseState != null) { doParse(parseState); // update any changed lexemes for (IRange range : parseState.getUpdateRegions()) { FileService.this._redrawListener.redrawRange(range); } } } } } } }); } /** * addLanguageService * * @param mimeType * @param service */ public void addLanguageService(String mimeType, IFileLanguageService service) { if (this._languageServices.containsKey(mimeType)) { throw new IllegalStateException(Messages.FileService_LanguageServiceRegistered); } this._languageServices.put(mimeType, service); } /** * setRedrawRangeListener * * @param listener */ public void setRedrawRangeListener(IRedrawRangeListener listener) { this._redrawListener = listener; } /** * setScanner * * @param parser */ public void setScanner(IParser parser) { this._scanner = parser; } /** * setParser * * @param parser */ public void setParser(IParser parser) { this._parser = parser; } /** * @see com.aptana.ide.editors.unified.IFileService#getLanguageService(java.lang.String) */ public IFileLanguageService getLanguageService(String mimeType) { return this._languageServices.get(mimeType); } /** * @see com.aptana.ide.editors.unified.IFileService#connectSourceProvider(com.aptana.ide.editors.unified.IFileSourceProvider) */ public void connectSourceProvider(final IFileSourceProvider sourceProvider) { if (this._connectedSourceProvider != null) { throw new IllegalStateException(StringUtils.format(Messages.FileService_CantConnetSourceProvider, new String[] { sourceProvider.getSourceURI(), _connectedSourceProvider.getSourceURI() })); } this._connectedSourceProvider = sourceProvider; visitLanguageServices(new IFileLanguageServiceVisitor() { public void visit(IFileLanguageService service) { service.connectSourceProvider(sourceProvider); } }); doFullParse(); } /** * @see com.aptana.ide.editors.unified.IFileService#disconnectSourceProvider(com.aptana.ide.editors.unified.IFileSourceProvider) */ public void disconnectSourceProvider(final IFileSourceProvider sourceProvider) { if (sourceProvider != null) { if (this._connectedSourceProvider != sourceProvider) { if (this._connectedSourceProvider != null && sourceProvider != null) { throw new IllegalStateException(StringUtils.format( Messages.FileService_SourceProviderAlreadyConnected, new String[] { sourceProvider.getSourceURI(), _connectedSourceProvider.getSourceURI() })); } else { throw new IllegalStateException(Messages.FileService_SourceProviderNotConnected); } } } if (getSourceProvider() != null) { FileContextManager.remove(getSourceProvider().getSourceURI()); } IdleFileChangedNotifier.instance().removeContentChangedEvent(this); LongIdleFileChangedNotifier.instance().removeContentChangedEvent(this); this._listeners.clear(); this._delayedListeners.clear(); this._longDelayedListeners.clear(); this._errorListeners.clear(); Object[] vals = this._languageServices.values().toArray(); for (int i = 0; i < vals.length; i++) { IFileLanguageService element = (IFileLanguageService) vals[i]; element.disconnectSourceProvider(sourceProvider); } this._languageServices.clear(); visitLanguageServices(new IFileLanguageServiceVisitor() { public void visit(IFileLanguageService service) { service.disconnectSourceProvider(sourceProvider); } }); if (this._errorManager != null) { removeLongDelayedFileListener(this._errorManager); this._errorManager = null; } this._partitionScanner = null; this._defaultSourceProvider = null; this._connectedSourceProvider = null; this._parseState = null; this._parser = null; this._parserLock = null; this._partitions = null; this._errorMarkers = null; } /** * @return FileSourceProvider */ public IFileSourceProvider getSourceProvider() { return this._connectedSourceProvider != null ? this._connectedSourceProvider : this._defaultSourceProvider; } /** * @see com.aptana.ide.editors.unified.IFileService#isConnected() */ public boolean isConnected() { return this._connectedSourceProvider != null; } /** * @see com.aptana.ide.editors.unified.IFileService#updateContent(java.lang.String, int, int) */ public void updateContent(String insertedSource, int offset, int removeLength) { IFileSourceProvider sourceProvider = getSourceProvider(); String fullSource; try { IParseState parseState = this.getParseState(); if (parseState != null) { fullSource = sourceProvider.getSource(); parseState.setEditState(fullSource, insertedSource, offset, removeLength); } synchronized (this) { synchronized (this._parserLock) { IPartitionScanner partitionScanner = this.getPartitioner(); if (partitionScanner != null) { partitionScanner.startPartitionScan(); } if (parseState != null) { if (this._scanner != null) { this.doFastParse(parseState); } else { this.doParse(parseState); } } if (partitionScanner != null) { this._partitions = partitionScanner.endPartitionScan(); } } } fireContentChangedEvent(insertedSource, offset, removeLength); } catch (IOException e) { IdeLog.logInfo(UnifiedEditorsPlugin.getDefault(), Messages.FileService_UpdateContentFailed, e); } } /** * @see com.aptana.ide.editors.unified.IFileService#getLexemeList() */ public LexemeList getLexemeList() { if (this._parseState == null) { return null; } return this._parseState.getLexemeList(); } /** * Gets the partitioner for this language * * @return Gets the partitioner for this language */ protected IPartitionScanner getPartitioner() { if (this._partitionScanner == null) { this._partitionScanner = new UnifiedPartitionScanner(this, getDefaultLanguage()); } return this._partitionScanner; } /** * @see com.aptana.ide.editors.unified.IFileService#getPartitions() */ public ITypedRegion[] getPartitions() { return this._partitions; } /** * setPartitions * * @param partitions */ public void setPartitions(ITypedRegion[] partitions) { this._partitions = partitions; } /** * @see com.aptana.ide.editors.unified.IFileService#getPartitionAtOffset(int) */ public ITypedRegion getPartitionAtOffset(int offset) { ITypedRegion result = null; for (int i = 0; i < this._partitions.length; i++) { ITypedRegion p = this._partitions[i]; if (p.getOffset() <= offset && p.getOffset() + p.getLength() >= offset) { result = p; break; } } return result; } /** * @see com.aptana.ide.editors.unified.IFileService#getParseState() */ public IParseState getParseState() { return this._parseState; } /** * Parses the file when first connecting to the document */ public void doFullParse() { IFileSourceProvider sourceProvider = getSourceProvider(); String fullSource; try { fullSource = sourceProvider.getSource(); synchronized (this) { synchronized (this._parserLock) { IPartitionScanner partitionScanner = this.getPartitioner(); IParseState parseState = this.getParseState(); if (partitionScanner != null) { partitionScanner.startPartitionScan(); } if (parseState != null) { LexemeList lexemes = parseState.getLexemeList(); synchronized (lexemes) { lexemes.clear(); parseState.setEditState(fullSource, fullSource, 0, 0); this.doParse(parseState); parseState.clearEditState(); } } if (partitionScanner != null) { this._partitions = partitionScanner.endPartitionScan(); } } } forceContentChangedEvent(); // always start with cursor at 0 } catch (IOException e) { IdeLog.logInfo(UnifiedEditorsPlugin.getDefault(), Messages.FileService_DoFullParseFailed, e); } } /** * doParse * * @param parseState */ private void doParse(IParseState parseState) { LexemeList lexemeList = parseState.getLexemeList(); synchronized (lexemeList) { if (this._parser != null) { try { // save old language change listener ILanguageChangeListener oldEventHandler = this._parser.getLanguageChangeListener(); // attach language change listener for partitioning this._parser.setLanguageChangeListener(this._languageChangeListener); // perform parse this._parser.parse(parseState); // restore old language change listener this._parser.setLanguageChangeListener(oldEventHandler); } catch (LexerException e) { IdeLog.logError(UnifiedEditorsPlugin.getDefault(), Messages.FileService_DoParseFailedLexer, e); } catch (ParseException e) { IdeLog.logError(UnifiedEditorsPlugin.getDefault(), Messages.FileService_DoParseFailedParse, e); } } } } /** * doFastParse * * @param parseState */ private void doFastParse(IParseState parseState) { LexemeList lexemeList = parseState.getLexemeList(); synchronized (lexemeList) { if (this._scanner != null) { try { // save old language change listener ILanguageChangeListener oldEventHandler = this._scanner.getLanguageChangeListener(); // attach language change listener for partitioning this._scanner.setLanguageChangeListener(this._languageChangeListener); // perform parse this._scanner.parse(parseState); // restore old language change listener this._scanner.setLanguageChangeListener(oldEventHandler); } catch (LexerException e) { IdeLog.logError(UnifiedEditorsPlugin.getDefault(), Messages.FileService_DoParseFailedLexer, e); } catch (ParseException e) { IdeLog.logError(UnifiedEditorsPlugin.getDefault(), Messages.FileService_DoParseFailedParse, e); } } } } /** * @see com.aptana.ide.editors.unified.IFileService#addFileListener(com.aptana.ide.editors.unified.IFileContextListener) */ public void addFileListener(IFileContextListener fileListener) { synchronized (this._listeners) { this._listeners.add(fileListener); } } /** * @see com.aptana.ide.editors.unified.IFileService#removeFileListener(com.aptana.ide.editors.unified.IFileContextListener) */ public void removeFileListener(IFileContextListener fileListener) { synchronized (this._listeners) { this._listeners.remove(fileListener); } } /** * @see com.aptana.ide.editors.unified.IFileService#addDelayedFileListener(com.aptana.ide.editors.unified.IFileContextListener) */ public void addDelayedFileListener(IFileContextListener fileListener) { synchronized (this._delayedListeners) { this._delayedListeners.add(fileListener); } } /** * @see com.aptana.ide.editors.unified.IFileService#removeDelayedFileListener(com.aptana.ide.editors.unified.IFileContextListener) */ public void removeDelayedFileListener(IFileContextListener fileListener) { synchronized (this._delayedListeners) { this._delayedListeners.remove(fileListener); } } /** * @see com.aptana.ide.editors.unified.IFileService#addLongDelayedFileListener(com.aptana.ide.editors.unified.IFileContextListener) */ public void addLongDelayedFileListener(IFileContextListener fileListener) { synchronized (this._longDelayedListeners) { this._longDelayedListeners.add(fileListener); } } /** * @see com.aptana.ide.editors.unified.IFileService#removeLongDelayedFileListener(com.aptana.ide.editors.unified.IFileContextListener) */ public void removeLongDelayedFileListener(IFileContextListener fileListener) { synchronized (this._longDelayedListeners) { this._longDelayedListeners.remove(fileListener); } } /** * Event fired every time content is updates * * @param insertedSource * @param offset * @param removeLength */ public void fireContentChangedEvent(String insertedSource, int offset, int removeLength) { FileContextContentEvent evt = createContentChangedEventAndFire(insertedSource, offset, removeLength); // Now fire the event to idle listeners IdleFileChangedNotifier.instance().queueContentChangedEvent(this, evt); // Now fire the event to long idle listeners LongIdleFileChangedNotifier.instance().queueContentChangedEvent(this, evt); } /** * Event fired to "refresh" the current editor. This might be a candidate for removal. */ public void forceContentChangedEvent() { fireContentChangedEvent(StringUtils.EMPTY, 0, 0); } /** * createContentChangedEventAndFire * * @param insertedSource * @param offset * @param removeLength * @return FileContextContentEvent */ private FileContextContentEvent createContentChangedEventAndFire(String insertedSource, int offset, int removeLength) { IFileContextListener[] listenerArray; synchronized (this._listeners) { listenerArray = this._listeners.toArray(new IFileContextListener[this._listeners.size()]); } int srcLen = insertedSource == null ? 0 : insertedSource.length(); int finalOffset = offset + srcLen - removeLength; FileContextContentEvent evt = new FileContextContentEvent(this, finalOffset); // Fire event to listeners fireContentChangedEvent(listenerArray, evt); return evt; } /** * fires the ContentChangedEvent to delayed listeners * * @param evt */ protected void fireDelayedContentChangedEvent(FileContextContentEvent evt) { IFileContextListener[] listenerArray; synchronized (this._delayedListeners) { listenerArray = this._delayedListeners.toArray(new IFileContextListener[this._delayedListeners.size()]); } this.fireContentChangedEvent(listenerArray, evt); } /** * fires the ContentChangedEvent to delayed listeners * * @param evt */ protected void fireLongDelayedContentChangedEvent(FileContextContentEvent evt) { IFileContextListener[] listenerArray; synchronized (this._longDelayedListeners) { listenerArray = this._longDelayedListeners.toArray(new IFileContextListener[this._longDelayedListeners .size()]); } this.fireContentChangedEvent(listenerArray, evt); } /** * fireContentChangedEvent * * @param listenerArray * @param evt */ private void fireContentChangedEvent(IFileContextListener[] listenerArray, FileContextContentEvent evt) { for (IFileContextListener listener : listenerArray) { try { listener.onContentChanged(evt); } catch (Exception e) { IdeLog.logInfo(UnifiedEditorsPlugin.getDefault(), Messages.FileService_ErrorOnFireChangedEvent, e); } } } /** * @see com.aptana.ide.editors.unified.IFileService#getSource() */ public String getSource() { try { return getSourceProvider().getSource(); } catch (IOException e) { IdeLog.logInfo(UnifiedEditorsPlugin.getDefault(), Messages.FileService_GetSourceFailed, e); return StringUtils.EMPTY; } } /** * Returns the dominate language for this language type (eg "text/html" for html, rather than "text/javascript" etc) * * @return Returns the dominate language for this language type */ public String getDefaultLanguage() { return this._defaultLanguage; } /** * setLanguageChangeListener * * @param languageChangeListener */ public void setLanguageChangeListener(IPartitionScanner languageChangeListener) { this._languageChangeListener = languageChangeListener; } /** * @see com.aptana.ide.editors.unified.IFileService#setFileErrors(com.aptana.ide.editors.unified.errors.IFileError[]) */ public void setFileErrors(IFileError[] markers) { this._errorMarkers = markers; fireErrorsChanged(); } /** * @see com.aptana.ide.editors.unified.IFileService#getFileErrors() */ public IFileError[] getFileErrors() { return this._errorMarkers; } /** * @see com.aptana.ide.editors.unified.IFileService#addErrorListener(com.aptana.ide.editors.unified.errors.IFileErrorListener) */ public void addErrorListener(IFileErrorListener listener) { synchronized (this._errorListeners) { this._errorListeners.add(listener); } } /** * @see com.aptana.ide.editors.unified.IFileService#removeErrorListener(com.aptana.ide.editors.unified.errors.IFileErrorListener) */ public void removeErrorListener(IFileErrorListener listener) { synchronized (this._errorListeners) { this._errorListeners.remove(listener); } } /** * fireErrorsChanged */ public void fireErrorsChanged() { IFileErrorListener[] listeners = null; synchronized (this._errorListeners) { listeners = this._errorListeners.toArray(new IFileErrorListener[this._errorListeners.size()]); } for (int i = 0; i < listeners.length; i++) { IFileErrorListener element = listeners[i]; element.onErrorsChanged(this._errorMarkers); } } /** * setErrorManager * * @param errorManager */ public void setErrorManager(IErrorManager errorManager) { if (this._errorManager != null) { removeLongDelayedFileListener(this._errorManager); } this._errorManager = errorManager; if (this._errorManager != null) { addLongDelayedFileListener(this._errorManager); } } /** * @see com.aptana.ide.editors.unified.IFileService#activateForEditing() */ public void activateForEditing() { visitLanguageServices(new IFileLanguageServiceVisitor() { public void visit(IFileLanguageService service) { service.activateForEditing(); } }); } /** * @see com.aptana.ide.editors.unified.IFileService#deactivateForEditing() */ public void deactivateForEditing() { visitLanguageServices(new IFileLanguageServiceVisitor() { public void visit(IFileLanguageService service) { service.deactivateForEditing(); } }); } private void visitLanguageServices(IFileLanguageServiceVisitor v) { Iterator<IFileLanguageService> it = this._languageServices.values().iterator(); while (it.hasNext()) { IFileLanguageService fls = it.next(); v.visit(fls); } } /** * @author Robin Debreuil */ interface IFileLanguageServiceVisitor { /** * visit * * @param service */ void visit(IFileLanguageService service); } /** * @see com.aptana.ide.editors.unified.IFileService#getFileListeners() */ public IFileContextListener[] getFileListeners() { return this._listeners.toArray(new IFileContextListener[this._listeners.size()]); } /** * @see com.aptana.ide.editors.unified.IFileService#getDelayedFileListeners() */ public IFileContextListener[] getDelayedFileListeners() { return this._delayedListeners.toArray(new IFileContextListener[this._delayedListeners.size()]); } /** * @see com.aptana.ide.editors.unified.IFileService#getLongDelayedFileListeners() */ public IFileContextListener[] getLongDelayedFileListeners() { return this._longDelayedListeners.toArray(new IFileContextListener[this._longDelayedListeners.size()]); } /** * @see com.aptana.ide.editors.unified.IFileService#hasFileListenerAdded(com.aptana.ide.editors.unified.IFileContextListener) */ public boolean hasFileListenerAdded(IFileContextListener listener) { return (this._listeners.contains(listener)); } /** * @see com.aptana.ide.editors.unified.IFileService#hasDelayedFileListenerAdded(com.aptana.ide.editors.unified.IFileContextListener) */ public boolean hasDelayedFileListenerAdded(IFileContextListener listener) { return (this._delayedListeners.contains(listener)); } /** * @see com.aptana.ide.editors.unified.IFileService#hasLongDelayedFileListenerAdded(com.aptana.ide.editors.unified.IFileContextListener) */ public boolean hasLongDelayedFileListenerAdded(IFileContextListener listener) { return (this._longDelayedListeners.contains(listener)); } }