/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.context; import com.google.dart.engine.source.Source; import com.google.dart.engine.source.SourceContainer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Instances of the class {@code ChangeSet} indicate which sources have been added, changed, * removed, or deleted. In the case of a changed source, there are multiple ways of indicating the * nature of the change. * <p> * No source should be added to the change set more than once, either with the same or a different * kind of change. It does not make sense, for example, for a source to be both added and removed, * and it is redundant for a source to be marked as changed in its entirety and changed in some * specific range. * * @coverage dart.engine */ public class ChangeSet { /** * Instances of the class {@code ContentChange} represent a change to the content of a source. */ public static class ContentChange { /** * The new contents of the source. */ private String contents; /** * The offset into the current contents. */ private int offset; /** * The number of characters in the original contents that were replaced */ private int oldLength; /** * The number of characters in the replacement text. */ private int newLength; /** * Initialize a newly created change object to represent a change to the content of a source. * * @param contents the new contents of the source * @param offset the offset into the current contents * @param oldLength the number of characters in the original contents that were replaced * @param newLength the number of characters in the replacement text */ public ContentChange(String contents, int offset, int oldLength, int newLength) { this.contents = contents; this.offset = offset; this.oldLength = oldLength; this.newLength = newLength; } /** * Return the new contents of the source. * * @return the new contents of the source */ public String getContents() { return contents; } /** * Return the number of characters in the replacement text. * * @return the number of characters in the replacement text */ public int getNewLength() { return newLength; } /** * Return the offset into the current contents. * * @return the offset into the current contents */ public int getOffset() { return offset; } /** * Return the number of characters in the original contents that were replaced. * * @return the number of characters in the original contents that were replaced */ public int getOldLength() { return oldLength; } } /** * A list containing the sources that have been added. */ private ArrayList<Source> addedSources = new ArrayList<Source>(); /** * A list containing the sources that have been changed. */ private ArrayList<Source> changedSources = new ArrayList<Source>(); /** * A table mapping the sources whose content has been changed to the current content of those * sources. */ private HashMap<Source, String> changedContent = new HashMap<Source, String>(); /** * A table mapping the sources whose content has been changed within a single range to the current * content of those sources and information about the affected range. */ private HashMap<Source, ContentChange> changedRanges = new HashMap<Source, ContentChange>(); /** * A list containing the sources that have been removed. */ private ArrayList<Source> removedSources = new ArrayList<Source>(); /** * A list containing the source containers specifying additional sources that have been removed. */ private ArrayList<SourceContainer> removedContainers = new ArrayList<SourceContainer>(); /** * A list containing the sources that have been deleted. */ private ArrayList<Source> deletedSources = new ArrayList<Source>(); /** * Initialize a newly created change set to be empty. */ public ChangeSet() { super(); } /** * Record that the specified source has been added and that its content is the default contents of * the source. * * @param source the source that was added */ public void addedSource(Source source) { addedSources.add(source); } /** * Record that the specified source has been changed and that its content is the given contents. * * @param source the source that was changed * @param contents the new contents of the source, or {@code null} if the default contents of the * source are to be used */ public void changedContent(Source source, String contents) { changedContent.put(source, contents); } /** * Record that the specified source has been changed and that its content is the given contents. * * @param source the source that was changed * @param contents the new contents of the source * @param offset the offset into the current contents * @param oldLength the number of characters in the original contents that were replaced * @param newLength the number of characters in the replacement text */ public void changedRange(Source source, String contents, int offset, int oldLength, int newLength) { changedRanges.put(source, new ContentChange(contents, offset, oldLength, newLength)); } /** * Record that the specified source has been changed. If the content of the source was previously * overridden, this has no effect (the content remains overridden). To cancel (or change) the * override, use {@link #changedContent(Source, String)} instead. * * @param source the source that was changed */ public void changedSource(Source source) { changedSources.add(source); } /** * Record that the specified source has been deleted. * * @param source the source that was deleted */ public void deletedSource(Source source) { deletedSources.add(source); } /** * Return a collection of the sources that have been added. * * @return a collection of the sources that have been added */ public List<Source> getAddedSources() { return addedSources; } /** * Return a table mapping the sources whose content has been changed to the current content of * those sources. * * @return a table mapping the sources whose content has been changed to the current content of * those sources */ public Map<Source, String> getChangedContents() { return changedContent; } /** * Return a table mapping the sources whose content has been changed within a single range to the * current content of those sources and information about the affected range. * * @return a table mapping sources to information about the changes to them */ public HashMap<Source, ContentChange> getChangedRanges() { return changedRanges; } /** * Return a collection of sources that have been changed. * * @return a collection of sources that have been changed */ public List<Source> getChangedSources() { return changedSources; } /** * Return a collection of sources that have been deleted. * * @return a collection of sources that have been deleted */ public List<Source> getDeletedSources() { return deletedSources; } /** * Return a list containing the source containers that were removed. * * @return a list containing the source containers that were removed */ public List<SourceContainer> getRemovedContainers() { return removedContainers; } /** * Return a list containing the sources that were removed. * * @return a list containing the sources that were removed */ public List<Source> getRemovedSources() { return removedSources; } /** * Return {@code true} if this change set does not contain any changes. * * @return {@code true} if this change set does not contain any changes */ public boolean isEmpty() { return addedSources.isEmpty() && changedSources.isEmpty() && changedContent.isEmpty() && changedRanges.isEmpty() && removedSources.isEmpty() && removedContainers.isEmpty() && deletedSources.isEmpty(); } /** * Record that the specified source container has been removed. * * @param container the source container that was removed */ public void removedContainer(SourceContainer container) { if (container != null) { removedContainers.add(container); } } /** * Record that the specified source has been removed. * * @param source the source that was removed */ public void removedSource(Source source) { if (source != null) { removedSources.add(source); } } @Override public String toString() { StringBuilder builder = new StringBuilder(); boolean needsSeparator = appendSources(builder, addedSources, false, "addedSources"); needsSeparator = appendSources(builder, changedSources, needsSeparator, "changedSources"); needsSeparator = appendSources(builder, changedContent, needsSeparator, "changedContent"); needsSeparator = appendSources(builder, changedRanges, needsSeparator, "changedRanges"); needsSeparator = appendSources(builder, deletedSources, needsSeparator, "deletedSources"); needsSeparator = appendSources(builder, removedSources, needsSeparator, "removedSources"); int count = removedContainers.size(); if (count > 0) { if (removedSources.isEmpty()) { if (needsSeparator) { builder.append("; "); } builder.append("removed: from "); builder.append(count); builder.append(" containers"); } else { builder.append(", and more from "); builder.append(count); builder.append(" containers"); } } return builder.toString(); } /** * Append the given sources to the given builder, prefixed with the given label and possibly a * separator. * * @param builder the builder to which the sources are to be appended * @param sources the sources to be appended * @param needsSeparator {@code true} if a separator is needed before the label * @param label the label used to prefix the sources * @return {@code true} if future lists of sources will need a separator */ private boolean appendSources(StringBuilder builder, ArrayList<Source> sources, boolean needsSeparator, String label) { if (sources.isEmpty()) { return needsSeparator; } if (needsSeparator) { builder.append("; "); } builder.append(label); String prefix = " "; for (Source source : sources) { builder.append(prefix); builder.append(source.getFullName()); prefix = ", "; } return true; } /** * Append the given sources to the given builder, prefixed with the given label and possibly a * separator. * * @param builder the builder to which the sources are to be appended * @param sources the sources to be appended * @param needsSeparator {@code true} if a separator is needed before the label * @param label the label used to prefix the sources * @return {@code true} if future lists of sources will need a separator */ private boolean appendSources(StringBuilder builder, HashMap<Source, ?> sources, boolean needsSeparator, String label) { if (sources.isEmpty()) { return needsSeparator; } if (needsSeparator) { builder.append("; "); } builder.append(label); String prefix = " "; for (Source source : sources.keySet()) { builder.append(prefix); builder.append(source.getFullName()); prefix = ", "; } return true; } }