Update of /cvsroot/rubyeclipse/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7305/src/org/rubypeople/rdt/internal/core Modified Files: RubyProjectElementInfo.java RubyElement.java RubyProject.java ReconcileWorkingCopyOperation.java RubyModelManager.java RubyScript.java Added Files: RubyElementDelta.java SimpleDelta.java RubyModelOperation.java DocumentAdapter.java RubyElementDeltaBuilder.java Assert.java DeltaProcessingState.java DeltaProcessor.java ModelUpdater.java LoadpathEntry.java Removed Files: LoadPathEntry.java Log Message: implement Ticket #46 (Update code folding on current working copy/editor) --- NEW FILE: DeltaProcessor.java --- package org.rubypeople.rdt.internal.core; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.PerformanceStats; [...1175 lines suppressed...] case IRubyElement.RUBY_MODEL: // case of a movedTo or movedFrom project (other cases are handled // in processResourceDelta(...) return IRubyElement.PROJECT; case NON_RUBY_RESOURCE: case IRubyElement.PROJECT: if (res.getType() == IResource.FOLDER) { return NON_RUBY_RESOURCE; } String fileName = res.getName(); if (Util.isValidRubyScriptName(fileName)) { return IRubyElement.SCRIPT; } else { return NON_RUBY_RESOURCE; } default: return NON_RUBY_RESOURCE; } } } --- NEW FILE: LoadpathEntry.java --- package org.rubypeople.rdt.internal.core; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.rubypeople.rdt.core.ILoadpathEntry; public class LoadpathEntry implements ILoadpathEntry { private static final String TYPE_PROJECT = "project"; private String rootID; private int entryKind; private IPath path; /** * Patterns allowing to include/exclude portions of the resource tree * denoted by this entry path. */ private IPath[] inclusionPatterns; private char[][] fullInclusionPatternChars; private IPath[] exclusionPatterns; private char[][] fullExclusionPatternChars; private final static char[][] UNINIT_PATTERNS = new char[][] { "Non-initialized yet".toCharArray()}; //$NON-NLS-1$ /* * Default inclusion pattern set */ public final static IPath[] INCLUDE_ALL = {}; /* * Default exclusion pattern set */ public final static IPath[] EXCLUDE_NONE = {}; private IProject project; /** * The export flag */ private boolean isExported; public LoadpathEntry(IProject project) { this(ILoadpathEntry.CPE_PROJECT, project.getFullPath(), INCLUDE_ALL, EXCLUDE_NONE, true); this.project = project; } public LoadpathEntry(int entryKind, IPath path, IPath[] inclusionPatterns, IPath[] exclusionPatterns, boolean isExported) { this.path = path; this.entryKind = entryKind; this.inclusionPatterns = inclusionPatterns; this.exclusionPatterns = exclusionPatterns; if (inclusionPatterns != INCLUDE_ALL && inclusionPatterns.length > 0) { this.fullInclusionPatternChars = UNINIT_PATTERNS; } if (exclusionPatterns.length > 0) { this.fullExclusionPatternChars = UNINIT_PATTERNS; } this.isExported = isExported; } public IPath getPath() { return path; } // FIXME We shouldn't need this! public IProject getProject() { return this.project; } public int getEntryKind() { return this.entryKind; } /** * Returns a <code>String</code> for the kind of a class path entry. */ static String kindToString(int kind) { switch (kind) { case ILoadpathEntry.CPE_PROJECT: return TYPE_PROJECT; //$NON-NLS-1$ case ILoadpathEntry.CPE_SOURCE: return "src"; //$NON-NLS-1$ case ILoadpathEntry.CPE_LIBRARY: return "lib"; //$NON-NLS-1$ case ILoadpathEntry.CPE_VARIABLE: return "var"; //$NON-NLS-1$ case ILoadpathEntry.CPE_CONTAINER: return "con"; //$NON-NLS-1$ default: return "unknown"; //$NON-NLS-1$ } } public String toXML() { StringBuffer buffer = new StringBuffer(); buffer.append("<pathentry type=\""); buffer.append(LoadpathEntry.kindToString(entryKind) + "\" "); buffer.append("path=\"" + getPath() + "\"/>"); return buffer.toString(); } /** * Answers an ID which is used to distinguish entries during package * fragment root computations */ public String rootID() { if (this.rootID == null) { switch (this.entryKind) { case ILoadpathEntry.CPE_LIBRARY: this.rootID = "[LIB]" + this.path; //$NON-NLS-1$ break; case ILoadpathEntry.CPE_PROJECT: this.rootID = "[PRJ]" + this.path; //$NON-NLS-1$ break; case ILoadpathEntry.CPE_SOURCE: this.rootID = "[SRC]" + this.path; //$NON-NLS-1$ break; case ILoadpathEntry.CPE_VARIABLE: this.rootID = "[VAR]" + this.path; //$NON-NLS-1$ break; case ILoadpathEntry.CPE_CONTAINER: this.rootID = "[CON]" + this.path; //$NON-NLS-1$ break; default: this.rootID = ""; //$NON-NLS-1$ break; } } return this.rootID; } /* * Returns a char based representation of the exclusions patterns full path. */ public char[][] fullExclusionPatternChars() { if (this.fullExclusionPatternChars == UNINIT_PATTERNS) { int length = this.exclusionPatterns.length; this.fullExclusionPatternChars = new char[length][]; IPath prefixPath = this.path.removeTrailingSeparator(); for (int i = 0; i < length; i++) { this.fullExclusionPatternChars[i] = prefixPath.append(this.exclusionPatterns[i]).toString().toCharArray(); } } return this.fullExclusionPatternChars; } /* * Returns a char based representation of the exclusions patterns full path. */ public char[][] fullInclusionPatternChars() { if (this.fullInclusionPatternChars == UNINIT_PATTERNS) { int length = this.inclusionPatterns.length; this.fullInclusionPatternChars = new char[length][]; IPath prefixPath = this.path.removeTrailingSeparator(); for (int i = 0; i < length; i++) { this.fullInclusionPatternChars[i] = prefixPath.append(this.inclusionPatterns[i]).toString().toCharArray(); } } return this.fullInclusionPatternChars; } /** * @see ILoadpathEntry#isExported() */ public boolean isExported() { return this.isExported; } /* (non-Javadoc) * @see org.rubypeople.rdt.core.ILoadpathEntry#getExclusionPatterns() */ public IPath[] getExclusionPatterns() { // TODO Auto-generated method stub return null; } /* (non-Javadoc) * @see org.rubypeople.rdt.core.ILoadpathEntry#getInclusionPatterns() */ public IPath[] getInclusionPatterns() { // TODO Auto-generated method stub return null; } } --- NEW FILE: DocumentAdapter.java --- /******************************************************************************* * Copyright (c) 2005 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.rubypeople.rdt.internal.core; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.rubypeople.rdt.core.IBuffer; /* * Adapts an IBuffer to IDocument */ public class DocumentAdapter extends Document { private IBuffer buffer; public DocumentAdapter(IBuffer buffer) { super(buffer.getContents()); this.buffer = buffer; } public void set(String text) { super.set(text); this.buffer.setContents(text); } public void replace(int offset, int length, String text) throws BadLocationException { super.replace(offset, length, text); this.buffer.replace(offset, length, text); } } --- LoadPathEntry.java DELETED --- Index: RubyScript.java =================================================================== RCS file: /cvsroot/rubyeclipse/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/RubyScript.java,v retrieving revision 1.17 retrieving revision 1.18 diff -C2 -d -r1.17 -r1.18 *** RubyScript.java 29 Nov 2005 19:38:00 -0000 1.17 --- RubyScript.java 13 Dec 2005 19:58:56 -0000 1.18 *************** *** 155,158 **** --- 155,171 ---- } + /** + * @see IRubyScript#getElementAt(int) + */ + public IRubyElement getElementAt(int position) throws RubyModelException { + + IRubyElement e= getSourceElementAt(position); + if (e == this) { + return null; + } else { + return e; + } + } + public String getElementName() { return this.name; *************** *** 234,238 **** */ public void reconcile() throws RubyModelException { ! reconcile(null, null); } --- 247,251 ---- */ public void reconcile() throws RubyModelException { ! reconcile(false, null, null); } *************** *** 242,251 **** * @see org.rubypeople.rdt.core.IRubyScript#reconcile() */ ! public void reconcile(WorkingCopyOwner workingCopyOwner, IProgressMonitor monitor) throws RubyModelException { if (!isWorkingCopy()) return; // Reconciling is not supported on non // working copies if (workingCopyOwner == null) workingCopyOwner = DefaultWorkingCopyOwner.PRIMARY; ! ReconcileWorkingCopyOperation op = new ReconcileWorkingCopyOperation(this, workingCopyOwner); op.runOperation(monitor); } --- 255,264 ---- * @see org.rubypeople.rdt.core.IRubyScript#reconcile() */ ! public void reconcile(boolean forceProblemDetection, WorkingCopyOwner workingCopyOwner, IProgressMonitor monitor) throws RubyModelException { if (!isWorkingCopy()) return; // Reconciling is not supported on non // working copies if (workingCopyOwner == null) workingCopyOwner = DefaultWorkingCopyOwner.PRIMARY; ! ReconcileWorkingCopyOperation op = new ReconcileWorkingCopyOperation(this, forceProblemDetection, workingCopyOwner); op.runOperation(monitor); } *************** *** 521,528 **** */ public void makeConsistent(IProgressMonitor monitor) throws RubyModelException { ! if (isConsistent()) return; ! ! openWhenClosed(createElementInfo(), monitor); } /* --- 534,557 ---- */ public void makeConsistent(IProgressMonitor monitor) throws RubyModelException { ! makeConsistent(false, monitor); } + + public RubyScript makeConsistent(boolean createAST, IProgressMonitor monitor) throws RubyModelException { + if (isConsistent()) return null; + + // create a new info and make it the current info + // (this will remove the info and its children just before storing the new infos) + // TODO When createAST is specified, actually do it! + // if (createAST) { + // ASTHolderCUInfo info = new ASTHolderCUInfo(); + // openWhenClosed(info, monitor); + // RubyScript result = info.ast; + // info.ast = null; + // return result; + // } else { + openWhenClosed(createElementInfo(), monitor); + return null; + // } + } /* Index: RubyProject.java =================================================================== RCS file: /cvsroot/rubyeclipse/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/RubyProject.java,v retrieving revision 1.16 retrieving revision 1.17 diff -C2 -d -r1.16 -r1.17 *** RubyProject.java 29 Nov 2005 19:40:41 -0000 1.16 --- RubyProject.java 13 Dec 2005 19:58:55 -0000 1.17 *************** *** 1,7 **** --- 1,13 ---- package org.rubypeople.rdt.internal.core; + import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; + import java.io.File; + import java.io.FileInputStream; + import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; + import java.util.HashSet; + import java.util.Hashtable; import java.util.Iterator; import java.util.List; *************** *** 16,23 **** --- 22,34 ---- import org.eclipse.core.resources.IProjectNature; import org.eclipse.core.resources.IResource; + import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; + import org.eclipse.core.runtime.Preferences; + import org.eclipse.core.runtime.preferences.IEclipsePreferences; + import org.eclipse.core.runtime.preferences.IScopeContext; + import org.osgi.service.prefs.BackingStoreException; import org.rubypeople.rdt.core.ILoadpathEntry; import org.rubypeople.rdt.core.IParent; *************** *** 40,43 **** --- 51,59 ---- protected List loadPathEntries; protected boolean scratched; + + /** + * Name of file containing custom project preferences + */ + private static final String PREF_FILENAME = ".rprefs"; //$NON-NLS-1$ /* *************** *** 181,184 **** --- 197,204 ---- } + private IPath getPluginWorkingLocation() { + return this.project.getWorkingLocation(RubyCore.PLUGIN_ID); + } + public IProject getProject() { return project; *************** *** 207,211 **** scratched = true; ! LoadPathEntry newEntry = new LoadPathEntry(anotherRubyProject); getLoadPathEntries().add(newEntry); } --- 227,231 ---- scratched = true; ! LoadpathEntry newEntry = new LoadpathEntry(anotherRubyProject); getLoadPathEntries().add(newEntry); } *************** *** 214,218 **** Iterator entries = getLoadPathEntries().iterator(); while (entries.hasNext()) { ! LoadPathEntry entry = (LoadPathEntry) entries.next(); if (entry.getEntryKind() == ILoadpathEntry.CPE_PROJECT && entry.getProject().getName().equals(anotherRubyProject.getName())) { --- 234,238 ---- Iterator entries = getLoadPathEntries().iterator(); while (entries.hasNext()) { ! LoadpathEntry entry = (LoadpathEntry) entries.next(); if (entry.getEntryKind() == ILoadpathEntry.CPE_PROJECT && entry.getProject().getName().equals(anotherRubyProject.getName())) { *************** *** 237,241 **** Iterator iterator = getLoadPathEntries().iterator(); while (iterator.hasNext()) { ! LoadPathEntry pathEntry = (LoadPathEntry) iterator.next(); if (pathEntry.getEntryKind() == ILoadpathEntry.CPE_PROJECT) referencedProjects.add(pathEntry.getProject()); --- 257,261 ---- Iterator iterator = getLoadPathEntries().iterator(); while (iterator.hasNext()) { ! LoadpathEntry pathEntry = (LoadpathEntry) iterator.next(); if (pathEntry.getEntryKind() == ILoadpathEntry.CPE_PROJECT) referencedProjects.add(pathEntry.getProject()); *************** *** 312,316 **** IPath referencedProjectPath = new Path(atts.getValue("path")); IProject referencedProject = getProject(referencedProjectPath.lastSegment()); ! loadPathEntries.add(new LoadPathEntry(referencedProject)); } } --- 332,336 ---- IPath referencedProjectPath = new Path(atts.getValue("path")); IProject referencedProject = getProject(referencedProjectPath.lastSegment()); ! loadPathEntries.add(new LoadpathEntry(referencedProject)); } } *************** *** 345,349 **** while (pathEntriesIterator.hasNext()) { ! LoadPathEntry entry = (LoadPathEntry) pathEntriesIterator.next(); buffer.append(entry.toXML()); } --- 365,369 ---- while (pathEntriesIterator.hasNext()) { ! LoadpathEntry entry = (LoadpathEntry) pathEntriesIterator.next(); buffer.append(entry.toXML()); } *************** *** 361,364 **** --- 381,423 ---- } + /** + * Returns the project custom preference pool. + * Project preferences may include custom encoding. + * @return IEclipsePreferences + */ + public IEclipsePreferences getEclipsePreferences(){ + if (!RubyProject.hasRubyNature(this.project)) return null; + // Get cached preferences if exist + RubyModelManager.PerProjectInfo perProjectInfo = RubyModelManager.getRubyModelManager().getPerProjectInfo(this.project, true); + if (perProjectInfo.preferences != null) return perProjectInfo.preferences; + // Init project preferences + IScopeContext context = new ProjectScope(getProject()); + final IEclipsePreferences eclipsePreferences = context.getNode(RubyCore.PLUGIN_ID); + updatePreferences(eclipsePreferences); + perProjectInfo.preferences = eclipsePreferences; + + // Listen to node removal from parent in order to reset cache (see bug 68993) + IEclipsePreferences.INodeChangeListener nodeListener = new IEclipsePreferences.INodeChangeListener() { + public void added(IEclipsePreferences.NodeChangeEvent event) { + // do nothing + } + public void removed(IEclipsePreferences.NodeChangeEvent event) { + if (event.getChild() == eclipsePreferences) { + RubyModelManager.getRubyModelManager().resetProjectPreferences(RubyProject.this); + } + } + }; + ((IEclipsePreferences) eclipsePreferences.parent()).addNodeChangeListener(nodeListener); + + // Listen to preference changes + IEclipsePreferences.IPreferenceChangeListener preferenceListener = new IEclipsePreferences.IPreferenceChangeListener() { + public void preferenceChange(IEclipsePreferences.PreferenceChangeEvent event) { + RubyModelManager.getRubyModelManager().resetProjectOptions(RubyProject.this); + } + }; + eclipsePreferences.addPreferenceChangeListener(preferenceListener); + return eclipsePreferences; + } + /* * (non-Rubydoc) *************** *** 514,517 **** --- 573,704 ---- return dest; } + + /** + * @see org.rubypeople.rdt.core.IRubyProject#getOption(String, boolean) + */ + public String getOption(String optionName, boolean inheritRubyCoreOptions) { + + String propertyName = optionName; + if (RubyModelManager.getRubyModelManager().optionNames.contains(propertyName)){ + IEclipsePreferences projectPreferences = getEclipsePreferences(); + String javaCoreDefault = inheritRubyCoreOptions ? RubyCore.getOption(propertyName) : null; + if (projectPreferences == null) return javaCoreDefault; + String value = projectPreferences.get(propertyName, javaCoreDefault); + return value == null ? null : value.trim(); + } + return null; + } + + /** + * @see org.rubypeople.rdt.core.IRubyProject#getOptions(boolean) + */ + public Map getOptions(boolean inheritRubyCoreOptions) { + + // initialize to the defaults from RubyCore options pool + Map options = inheritRubyCoreOptions ? RubyCore.getOptions() : new Hashtable(5); + + // Get project specific options + RubyModelManager.PerProjectInfo perProjectInfo = null; + Hashtable projectOptions = null; + HashSet optionNames = RubyModelManager.getRubyModelManager().optionNames; + try { + perProjectInfo = getPerProjectInfo(); + projectOptions = perProjectInfo.options; + if (projectOptions == null) { + // get eclipse preferences + IEclipsePreferences projectPreferences= getEclipsePreferences(); + if (projectPreferences == null) return options; // cannot do better (non-Ruby project) + // create project options + String[] propertyNames = projectPreferences.keys(); + projectOptions = new Hashtable(propertyNames.length); + for (int i = 0; i < propertyNames.length; i++){ + String propertyName = propertyNames[i]; + String value = projectPreferences.get(propertyName, null); + if (value != null && optionNames.contains(propertyName)){ + projectOptions.put(propertyName, value.trim()); + } + } + // cache project options + perProjectInfo.options = projectOptions; + } + } catch (RubyModelException jme) { + projectOptions = new Hashtable(); + } catch (BackingStoreException e) { + projectOptions = new Hashtable(); + } + + // Inherit from RubyCore options if specified + if (inheritRubyCoreOptions) { + Iterator propertyNames = projectOptions.keySet().iterator(); + while (propertyNames.hasNext()) { + String propertyName = (String) propertyNames.next(); + String propertyValue = (String) projectOptions.get(propertyName); + if (propertyValue != null && optionNames.contains(propertyName)){ + options.put(propertyName, propertyValue.trim()); + } + } + return options; + } + return projectOptions; + } + + /* + * Update eclipse preferences from old preferences. + */ + private void updatePreferences(IEclipsePreferences preferences) { + + Preferences oldPreferences = loadPreferences(); + if (oldPreferences != null) { + String[] propertyNames = oldPreferences.propertyNames(); + for (int i = 0; i < propertyNames.length; i++){ + String propertyName = propertyNames[i]; + String propertyValue = oldPreferences.getString(propertyName); + if (!"".equals(propertyValue)) { //$NON-NLS-1$ + preferences.put(propertyName, propertyValue); + } + } + try { + // save immediately old preferences + preferences.flush(); + } catch (BackingStoreException e) { + // fails silently + } + } + } + + /** + * load preferences from a shareable format (VCM-wise) + */ + private Preferences loadPreferences() { + + Preferences preferences = new Preferences(); + IPath projectMetaLocation = getPluginWorkingLocation(); + if (projectMetaLocation != null) { + File prefFile = projectMetaLocation.append(PREF_FILENAME).toFile(); + if (prefFile.exists()) { // load preferences from file + InputStream in = null; + try { + in = new BufferedInputStream(new FileInputStream(prefFile)); + preferences.load(in); + } catch (IOException e) { // problems loading preference store - quietly ignore + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { // ignore problems with close + } + } + } + // one shot read, delete old preferences + prefFile.delete(); + return preferences; + } + } + return null; + } + + public void resetCaches() { + // TODO Auto-generated method stub + } } --- NEW FILE: ModelUpdater.java --- /******************************************************************************* * 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 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.rubypeople.rdt.internal.core; import java.util.HashSet; import java.util.Iterator; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyElementDelta; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.RubyModelException; /** * This class is used by <code>RubyModelManager</code> to update the RubyModel * based on some <code>IRubyElementDelta</code>s. */ public class ModelUpdater { HashSet projectsToUpdate = new HashSet(); /** * Adds the given child handle to its parent's cache of children. */ protected void addToParentInfo(Openable child) { Openable parent = (Openable) child.getParent(); if (parent != null && parent.isOpen()) { try { RubyElementInfo info = (RubyElementInfo) parent.getElementInfo(); info.addChild(child); } catch (RubyModelException e) { // do nothing - we already checked if open } } } /** * Closes the given element, which removes it from the cache of open * elements. */ protected static void close(Openable element) { try { element.close(); } catch (RubyModelException e) { // do nothing } } /** * Processing for an element that has been added: * <ul> * <li>If the element is a project, do nothing, and do not process * children, as when a project is created it does not yet have any natures - * specifically a java nature. * <li>If the elemet is not a project, process it as added (see * <code>basicElementAdded</code>. * </ul> */ protected void elementAdded(Openable element) { int elementType = element.getElementType(); if (elementType == IRubyElement.PROJECT) { // project add is handled by RubyProject.configure() because // when a project is created, it does not yet have a java nature addToParentInfo(element); this.projectsToUpdate.add(element); } else { addToParentInfo(element); // Force the element to be closed as it might have been opened // before the resource modification came in and it might have a new // child // For example, in an IWorkspaceRunnable: // 1. create a package fragment p using a java model operation // 2. open package p // 3. add file X.java in folder p // When the resource delta comes in, only the addition of p is // notified, // but the package p is already opened, thus its children are not // recomputed // and it appears empty. close(element); } } /** * Generic processing for elements with changed contents: * <ul> * <li>The element is closed such that any subsequent accesses will re-open * the element reflecting its new structure. * </ul> */ protected void elementChanged(Openable element) { close(element); } /** * Generic processing for a removed element: * <ul> * <li>Close the element, removing its structure from the cache * <li>Remove the element from its parent's cache of children * <li>Add a REMOVED entry in the delta * </ul> */ protected void elementRemoved(Openable element) { if (element.isOpen()) { close(element); } removeFromParentInfo(element); int elementType = element.getElementType(); switch (elementType) { case IRubyElement.RUBY_MODEL: // TODO Reset IndexManager when we have it integrated //RubyModelManager.getRubyModelManager().getIndexManager().reset(); break; case IRubyElement.PROJECT: RubyModelManager.getRubyModelManager().removePerProjectInfo((RubyProject) element); break; } } /** * Converts a <code>IResourceDelta</code> rooted in a * <code>Workspace</code> into the corresponding set of * <code>IRubyElementDelta</code>, rooted in the relevant * <code>RubyModel</code>s. */ public void processRubyDelta(IRubyElementDelta delta) { // if (DeltaProcessor.VERBOSE){ // System.out.println("UPDATING Model with Delta: // ["+Thread.currentThread()+":" + delta + "]:"); // } try { this.traverseDelta(delta, null); // traverse delta // update package fragment roots of projects that were affected Iterator iterator = this.projectsToUpdate.iterator(); while (iterator.hasNext()) { RubyProject project = (RubyProject) iterator.next(); // TODO Update package fragemnt roots for the project?? //project.updatePackageFragmentRoots(); } } finally { this.projectsToUpdate = new HashSet(); } } /** * Removes the given element from its parents cache of children. If the * element does not have a parent, or the parent is not currently open, this * has no effect. */ protected void removeFromParentInfo(Openable child) { Openable parent = (Openable) child.getParent(); if (parent != null && parent.isOpen()) { try { RubyElementInfo info = (RubyElementInfo) parent.getElementInfo(); info.removeChild(child); } catch (RubyModelException e) { // do nothing - we already checked if open } } } /** * Converts an <code>IResourceDelta</code> and its children into the * corresponding <code>IRubyElementDelta</code>s. Return whether the * delta corresponds to a resource on the classpath. If it is not a resource * on the classpath, it will be added as a non-java resource by the sender * of this method. */ protected void traverseDelta(IRubyElementDelta delta, IRubyProject project) { boolean processChildren = true; Openable element = (Openable) delta.getElement(); switch (element.getElementType()) { case IRubyElement.PROJECT: project = (IRubyProject) element; break; case IRubyElement.SCRIPT: // filter out working copies that are not primary (we don't want to // add/remove them to/from the package fragment RubyScript cu = (RubyScript) element; if (cu.isWorkingCopy() && !cu.isPrimary()) { return; } } switch (delta.getKind()) { case IRubyElementDelta.ADDED: elementAdded(element); break; case IRubyElementDelta.REMOVED: elementRemoved(element); break; case IRubyElementDelta.CHANGED: if ((delta.getFlags() & IRubyElementDelta.F_CONTENT) != 0) { elementChanged(element); } break; } if (processChildren) { IRubyElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { IRubyElementDelta childDelta = children[i]; this.traverseDelta(childDelta, project); } } } } Index: RubyProjectElementInfo.java =================================================================== RCS file: /cvsroot/rubyeclipse/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/RubyProjectElementInfo.java,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** RubyProjectElementInfo.java 2 Mar 2005 00:54:01 -0000 1.2 --- RubyProjectElementInfo.java 13 Dec 2005 19:58:55 -0000 1.3 *************** *** 62,67 **** if (projectPath.equals(entry.getPath())) { srcIsProject = true; ! inclusionPatterns = ((LoadPathEntry) entry).fullInclusionPatternChars(); ! exclusionPatterns = ((LoadPathEntry) entry).fullExclusionPatternChars(); break; } --- 62,67 ---- if (projectPath.equals(entry.getPath())) { srcIsProject = true; ! inclusionPatterns = ((LoadpathEntry) entry).fullInclusionPatternChars(); ! exclusionPatterns = ((LoadpathEntry) entry).fullExclusionPatternChars(); break; } Index: RubyModelManager.java =================================================================== RCS file: /cvsroot/rubyeclipse/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/RubyModelManager.java,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** RubyModelManager.java 16 Oct 2005 21:02:16 -0000 1.4 --- RubyModelManager.java 13 Dec 2005 19:58:55 -0000 1.5 *************** *** 7,16 **** --- 7,25 ---- import java.util.HashMap; import java.util.HashSet; + import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import org.eclipse.core.resources.IProject; + import org.eclipse.core.resources.IResourceChangeEvent; + import org.eclipse.core.resources.IWorkspace; [...1050 lines suppressed...] + if (option != null) RubyModelManager.verbose = option.equalsIgnoreCase("true"); //$NON-NLS-1$ + + option = Platform.getDebugOption(POST_ACTION_DEBUG); + if (option != null) + RubyModelOperation.POST_ACTION_VERBOSE = option.equalsIgnoreCase("true"); //$NON-NLS-1$ + + option = Platform.getDebugOption(ENABLE_NEW_FORMATTER); + if (option != null) + DefaultCodeFormatter.USE_NEW_FORMATTER = option.equalsIgnoreCase("true"); //$NON-NLS-1$ + + // configure performance options + if (PerformanceStats.ENABLED) { + DeltaProcessor.PERF = PerformanceStats.isEnabled(DELTA_LISTENER_PERF); + ReconcileWorkingCopyOperation.PERF = PerformanceStats.isEnabled(RECONCILE_PERF); + } + } + + } + } --- NEW FILE: Assert.java --- /******************************************************************************* * 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 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.rubypeople.rdt.internal.core; /* This class is not intended to be instantiated. */ public final class Assert { private Assert() { // cannot be instantiated } /** Asserts that an argument is legal. If the given boolean is * not <code>true</code>, an <code>IllegalArgumentException</code> * is thrown. * * @param expression the outcode of the check * @return <code>true</code> if the check passes (does not return * if the check fails) * @exception IllegalArgumentException if the legality test failed */ public static boolean isLegal(boolean expression) { return isLegal(expression, ""); //$NON-NLS-1$ } /** Asserts that an argument is legal. If the given boolean is * not <code>true</code>, an <code>IllegalArgumentException</code> * is thrown. * The given message is included in that exception, to aid debugging. * * @param expression the outcode of the check * @param message the message to include in the exception * @return <code>true</code> if the check passes (does not return * if the check fails) * @exception IllegalArgumentException if the legality test failed */ public static boolean isLegal(boolean expression, String message) { if (!expression) throw new IllegalArgumentException(message); return expression; } /** Asserts that the given object is not <code>null</code>. If this * is not the case, some kind of unchecked exception is thrown. * * @param object the value to test * @exception IllegalArgumentException if the object is <code>null</code> */ public static void isNotNull(Object object) { isNotNull(object, ""); //$NON-NLS-1$ } /** Asserts that the given object is not <code>null</code>. If this * is not the case, some kind of unchecked exception is thrown. * The given message is included in that exception, to aid debugging. * * @param object the value to test * @param message the message to include in the exception * @exception IllegalArgumentException if the object is <code>null</code> */ public static void isNotNull(Object object, String message) { if (object == null) throw new AssertionFailedException("null argument; " + message); //$NON-NLS-1$ } /** Asserts that the given boolean is <code>true</code>. If this * is not the case, some kind of unchecked exception is thrown. * * @param expression the outcode of the check * @return <code>true</code> if the check passes (does not return * if the check fails) */ public static boolean isTrue(boolean expression) { return isTrue(expression, ""); //$NON-NLS-1$ } /** Asserts that the given boolean is <code>true</code>. If this * is not the case, some kind of unchecked exception is thrown. * The given message is included in that exception, to aid debugging. * * @param expression the outcode of the check * @param message the message to include in the exception * @return <code>true</code> if the check passes (does not return * if the check fails) */ public static boolean isTrue(boolean expression, String message) { if (!expression) throw new AssertionFailedException("Assertion failed; " + message); //$NON-NLS-1$ return expression; } public static class AssertionFailedException extends RuntimeException { private static final long serialVersionUID = -3179320974982211564L; // backward compatible public AssertionFailedException(String detail) { super(detail); } } } Index: RubyElement.java =================================================================== RCS file: /cvsroot/rubyeclipse/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/RubyElement.java,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** RubyElement.java 11 Mar 2005 01:59:48 -0000 1.4 --- RubyElement.java 13 Dec 2005 19:58:55 -0000 1.5 *************** *** 31,34 **** --- 31,35 ---- import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.PlatformObject; + import org.rubypeople.rdt.core.IField; import org.rubypeople.rdt.core.IOpenable; import org.rubypeople.rdt.core.IParent; *************** *** 39,42 **** --- 40,45 ---- import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.IRubyScript; + import org.rubypeople.rdt.core.ISourceRange; + import org.rubypeople.rdt.core.ISourceReference; import org.rubypeople.rdt.core.RubyModelException; *************** *** 127,130 **** --- 130,182 ---- return this; } + + /** + * Returns the element that is located at the given source position + * in this element. This is a helper method for <code>IRubyScript#getElementAt</code>, + * and only works on ruby scripts and types. The position given is + * known to be within this element's source range already, and if no finer + * grained element is found at the position, this element is returned. + */ + protected IRubyElement getSourceElementAt(int position) throws RubyModelException { + if (this instanceof ISourceReference) { + IRubyElement[] children = getChildren(); + for (int i = children.length-1; i >= 0; i--) { + IRubyElement aChild = children[i]; + if (aChild instanceof SourceRefElement) { + SourceRefElement child = (SourceRefElement) children[i]; + ISourceRange range = child.getSourceRange(); + int start = range.getOffset(); + int end = start + range.getLength(); + if (start <= position && position <= end) { + if (child instanceof IField) { + // check muti-declaration case (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=39943) + int declarationStart = start; + SourceRefElement candidate = null; + do { + // check name range + range = ((IField)child).getNameRange(); + if (position <= range.getOffset() + range.getLength()) { + candidate = child; + } else { + return candidate == null ? child.getSourceElementAt(position) : candidate.getSourceElementAt(position); + } + child = --i>=0 ? (SourceRefElement) children[i] : null; + } while (child != null && child.getSourceRange().getOffset() == declarationStart); + // position in field's type: use first field + return candidate.getSourceElementAt(position); + } else if (child instanceof IParent) { + return child.getSourceElementAt(position); + } else { + return child; + } + } + } + } + } else { + // should not happen + Assert.isTrue(false); + } + return this; + } /** --- NEW FILE: DeltaProcessingState.java --- package org.rubypeople.rdt.internal.core; import java.util.HashMap; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.Platform; import org.rubypeople.rdt.core.IElementChangedListener; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.internal.core.util.Util; public class DeltaProcessingState implements IResourceChangeListener { /* * Collection of listeners for Ruby element deltas */ public IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5]; public int[] elementChangedListenerMasks = new int[5]; public int elementChangedListenerCount = 0; /* * Collection of pre Ruby resource change listeners */ public IResourceChangeListener[] preResourceChangeListeners = new IResourceChangeListener[1]; public int[] preResourceChangeEventMasks = new int[1]; public int preResourceChangeListenerCount = 0; /* * The delta processor for the current thread. */ private ThreadLocal deltaProcessors = new ThreadLocal(); public IRubyProject[] modelProjectsCache; /* A table from IRubyProject to IRubyProject[] (the list of direct dependent of the key) */ public HashMap projectDependencies = new HashMap(); /* * Need to clone defensively the listener information, in case some listener * is reacting to some notification iteration by adding/changing/removing * any of the other (for example, if it deregisters itself). */ public void addElementChangedListener(IElementChangedListener listener, int eventMask) { for (int i = 0; i < this.elementChangedListenerCount; i++) { if (this.elementChangedListeners[i].equals(listener)) { // only clone the masks, since we could be in the middle of // notifications and one listener decide to change // any event mask of another listeners (yet not notified). int cloneLength = this.elementChangedListenerMasks.length; System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[cloneLength], 0, cloneLength); this.elementChangedListenerMasks[i] = eventMask; // could be // different return; } } // may need to grow, no need to clone, since iterators will have cached // original arrays and max boundary and we only add to the end. int length; if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount) { System.arraycopy(this.elementChangedListeners, 0, this.elementChangedListeners = new IElementChangedListener[length * 2], 0, length); System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[length * 2], 0, length); } this.elementChangedListeners[this.elementChangedListenerCount] = listener; this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask; this.elementChangedListenerCount++; } public void removeElementChangedListener(IElementChangedListener listener) { for (int i = 0; i < this.elementChangedListenerCount; i++) { if (this.elementChangedListeners[i].equals(listener)) { // need to clone defensively since we might be in the middle of // listener notifications (#fire) int length = this.elementChangedListeners.length; IElementChangedListener[] newListeners = new IElementChangedListener[length]; System.arraycopy(this.elementChangedListeners, 0, newListeners, 0, i); int[] newMasks = new int[length]; System.arraycopy(this.elementChangedListenerMasks, 0, newMasks, 0, i); // copy trailing listeners int trailingLength = this.elementChangedListenerCount - i - 1; if (trailingLength > 0) { System.arraycopy(this.elementChangedListeners, i + 1, newListeners, i, trailingLength); System.arraycopy(this.elementChangedListenerMasks, i + 1, newMasks, i, trailingLength); } // update manager listener state (#fire need to iterate over // original listeners through a local variable to hold onto // the original ones) this.elementChangedListeners = newListeners; this.elementChangedListenerMasks = newMasks; this.elementChangedListenerCount--; return; } } } public DeltaProcessor getDeltaProcessor() { DeltaProcessor deltaProcessor = (DeltaProcessor) this.deltaProcessors.get(); if (deltaProcessor != null) return deltaProcessor; deltaProcessor = new DeltaProcessor(this, RubyModelManager.getRubyModelManager()); this.deltaProcessors.set(deltaProcessor); return deltaProcessor; } public void resourceChanged(final IResourceChangeEvent event) { for (int i = 0; i < this.preResourceChangeListenerCount; i++) { // wrap callbacks with Safe runnable for subsequent listeners to be // called when some are causing grief final IResourceChangeListener listener = this.preResourceChangeListeners[i]; if ((this.preResourceChangeEventMasks[i] & event.getType()) != 0) Platform.run(new ISafeRunnable() { public void handleException(Throwable exception) { Util .log(exception, "Exception occurred in listener of pre Java resource change notification"); //$NON-NLS-1$ } public void run() throws Exception { listener.resourceChanged(event); } }); } try { getDeltaProcessor().resourceChanged(event); } finally { // TODO (jerome) see 47631, may want to get rid of following so as // to reuse delta processor ? if (event.getType() == IResourceChangeEvent.POST_CHANGE) { this.deltaProcessors.set(null); } } } public void initializeRoots() { // TODO Do we actually need to do anything to initialize roots? } } --- NEW FILE: SimpleDelta.java --- /******************************************************************************* * 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 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.rubypeople.rdt.internal.core; import org.rubypeople.rdt.core.IRubyElementDelta; /** * A simple Ruby element delta that remembers the kind of changes only. */ public class SimpleDelta { /* * @see IRubyElementDelta#getKind() */ protected int kind = 0; /* * @see IRubyElementDelta#getFlags() */ protected int changeFlags = 0; /* * Marks this delta as added */ public void added() { this.kind = IRubyElementDelta.ADDED; } /* * Marks this delta as changed with the given change flag */ public void changed(int flags) { this.kind = IRubyElementDelta.CHANGED; this.changeFlags |= flags; } /* * @see IRubyElementDelta#getFlags() */ public int getFlags() { return this.changeFlags; } /* * @see IRubyElementDelta#getKind() */ public int getKind() { return this.kind; } /* * Mark this delta has a having a modifiers change */ public void modifiers() { changed(IRubyElementDelta.F_MODIFIERS); } /* * Marks this delta as removed */ public void removed() { this.kind = IRubyElementDelta.REMOVED; this.changeFlags = 0; } /* * Mark this delta has a having a super type change */ public void superTypes() { changed(IRubyElementDelta.F_SUPER_TYPES); } protected void toDebugString(StringBuffer buffer) { buffer.append("["); //$NON-NLS-1$ switch (getKind()) { case IRubyElementDelta.ADDED : buffer.append('+'); break; case IRubyElementDelta.REMOVED : buffer.append('-'); break; case IRubyElementDelta.CHANGED : buffer.append('*'); break; default : buffer.append('?'); break; } buffer.append("]: {"); //$NON-NLS-1$ toDebugString(buffer, getFlags()); buffer.append("}"); //$NON-NLS-1$ } protected boolean toDebugString(StringBuffer buffer, int flags) { boolean prev = false; if ((flags & IRubyElementDelta.F_MODIFIERS) != 0) { if (prev) buffer.append(" | "); //$NON-NLS-1$ buffer.append("MODIFIERS CHANGED"); //$NON-NLS-1$ prev = true; } if ((flags & IRubyElementDelta.F_SUPER_TYPES) != 0) { if (prev) buffer.append(" | "); //$NON-NLS-1$ buffer.append("SUPER TYPES CHANGED"); //$NON-NLS-1$ prev = true; } return prev; } public String toString() { StringBuffer buffer = new StringBuffer(); toDebugString(buffer); return buffer.toString(); } } --- NEW FILE: RubyElementDeltaBuilder.java --- /******************************************************************************* * 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 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.rubypeople.rdt.internal.core; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.rubypeople.rdt.core.IParent; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyElementDelta; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.internal.core.util.CharOperation; import org.rubypeople.rdt.internal.core.util.Util; /** * A java element delta biulder creates a java element delta on a java element * between the version of the java element at the time the comparator was * created and the current version of the java element. * * It performs this operation by locally caching the contents of the java * element when it is created. When the method createDeltas() is called, it * creates a delta over the cached contents and the new contents. */ public class RubyElementDeltaBuilder { /** * The java element handle */ IRubyElement javaElement; /** * The maximum depth in the java element children we should look into */ int maxDepth = Integer.MAX_VALUE; /** * The old handle to info relationships */ Map infos; /** * The old position info */ Map oldPositions; /** * The new position info */ Map newPositions; /** * Change delta */ RubyElementDelta delta; /** * List of added elements */ ArrayList added; /** * List of removed elements */ ArrayList removed; /** * Doubly linked list item */ class ListItem { public IRubyElement previous; public IRubyElement next; public ListItem(IRubyElement previous, IRubyElement next) { this.previous = previous; this.next = next; } } /** * Creates a java element comparator on a java element looking as deep as * necessary. */ public RubyElementDeltaBuilder(IRubyElement javaElement) { this.javaElement = javaElement; this.initialize(); this.recordElementInfo(javaElement, (RubyModel) this.javaElement.getRubyModel(), 0); } /** * Creates a java element comparator on a java element looking only * 'maxDepth' levels deep. */ public RubyElementDeltaBuilder(IRubyElement javaElement, int maxDepth) { this.javaElement = javaElement; this.maxDepth = maxDepth; this.initialize(); this.recordElementInfo(javaElement, (RubyModel) this.javaElement.getRubyModel(), 0); } /** * Repairs the positioning information after an element has been added */ private void added(IRubyElement element) { this.added.add(element); ListItem current = this.getNewPosition(element); ListItem previous = null, next = null; if (current.previous != null) previous = this.getNewPosition(current.previous); if (current.next != null) next = this.getNewPosition(current.next); if (previous != null) previous.next = current.next; if (next != null) next.previous = current.previous; } /** * Builds the java element deltas between the old content of the compilation * unit and its new content. */ public void buildDeltas() { this.recordNewPositions(this.javaElement, 0); this.findAdditions(this.javaElement, 0); this.findDeletions(); this.findChangesInPositioning(this.javaElement, 0); this.trimDelta(this.delta); if (this.delta.getAffectedChildren().length == 0) { // this is a fine grained but not children affected -> mark as // content changed this.delta.contentChanged(); } } private boolean equals(char[][][] first, char[][][] second) { if (first == second) return true; if (first == null || second == null) return false; if (first.length != second.length) return false; for (int i = first.length; --i >= 0;) if (!CharOperation.equals(first[i], second[i])) return false; return true; } /** * Finds elements which have been added or changed. */ private void findAdditions(IRubyElement newElement, int depth) { RubyElementInfo oldInfo = this.getElementInfo(newElement); if (oldInfo == null && depth < this.maxDepth) { this.delta.added(newElement); added(newElement); } else { this.removeElementInfo(newElement); } if (depth >= this.maxDepth) { // mark element as changed this.delta.changed(newElement, IRubyElementDelta.F_CONTENT); return; } RubyElementInfo newInfo = null; try { newInfo = (RubyElementInfo) ((RubyElement) newElement).getElementInfo(); } catch (RubyModelException npe) { return; } this.findContentChange(oldInfo, newInfo, newElement); if (oldInfo != null && newElement instanceof IParent) { IRubyElement[] children = newInfo.getChildren(); if (children != null) { int length = children.length; for (int i = 0; i < length; i++) { this.findAdditions(children[i], depth + 1); } } } } /** * Looks for changed positioning of elements. */ private void findChangesInPositioning(IRubyElement element, int depth) { if (depth >= this.maxDepth || this.added.contains(element) || this.removed.contains(element)) return; if (!isPositionedCorrectly(element)) { this.delta.changed(element, IRubyElementDelta.F_REORDER); } if (element instanceof IParent) { RubyElementInfo info = null; try { info = (RubyElementInfo) ((RubyElement) element).getElementInfo(); } catch (RubyModelException npe) { return; } IRubyElement[] children = info.getChildren(); if (children != null) { int length = children.length; for (int i = 0; i < length; i++) { this.findChangesInPositioning(children[i], depth + 1); } } } } /** * The elements are equivalent, but might have content changes. */ private void findContentChange(RubyElementInfo oldInfo, RubyElementInfo newInfo, IRubyElement newElement) { if (oldInfo instanceof MemberElementInfo && newInfo instanceof MemberElementInfo) { if (oldInfo instanceof RubyMethodElementInfo && newInfo instanceof RubyMethodElementInfo) { RubyMethodElementInfo oldSourceMethodInfo = (RubyMethodElementInfo) oldInfo; ... [truncated message content] |