blob: 04d835673ff40dfc542f17418ae3e6ea65ac8755 [file] [log] [blame]
/*
* 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.internal.cache;
import com.google.common.collect.Maps;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.utilities.collection.BooleanArray;
import com.google.dart.engine.utilities.instrumentation.Instrumentation;
import com.google.dart.engine.utilities.instrumentation.InstrumentationBuilder;
import com.google.dart.engine.utilities.source.LineInfo;
import java.util.Map;
/**
* Instances of the abstract class {@code SourceEntryImpl} implement the behavior common to all
* {@link SourceEntry source entries}.
*
* @coverage dart.engine
*/
public abstract class SourceEntryImpl implements SourceEntry {
/**
* The most recent time at which the state of the source matched the state represented by this
* entry.
*/
private long modificationTime;
/**
* A bit-encoding of boolean flags associated with this element.
*/
private int flags;
/**
* The exception that caused one or more values to have a state of {@link CacheState#ERROR}.
*/
private AnalysisException exception;
/**
* The state of the cached content.
*/
private CacheState contentState = CacheState.INVALID;
/**
* The content of the source, or {@code null} if the content is not currently cached.
*/
private CharSequence content;
/**
* The state of the cached line information.
*/
private CacheState lineInfoState = CacheState.INVALID;
/**
* The line information computed for the source, or {@code null} if the line information is not
* currently cached.
*/
private LineInfo lineInfo;
/**
* A table mapping data descriptors to a count of the number of times a value of that kind was
* transitioned from some {@link CacheState} to {@link CacheState#VALID}.
*/
public static final Map<DataDescriptor<?>, Map<CacheState, Integer>> transitionMap = Maps.newHashMap();
/**
* The index of the flag indicating whether the source was explicitly added to the context or
* whether the source was implicitly added because it was referenced by another source.
*/
private static final int EXPLICITLY_ADDED_FLAG = 0;
/**
* Initialize a newly created cache entry to be empty.
*/
public SourceEntryImpl() {
super();
}
/**
* Fix the state of the {@link #exception} to match the current state of the entry.
*/
public void fixExceptionState() {
if (hasErrorState()) {
if (exception == null) {
//
// This code should never be reached, but is a fail-safe in case an exception is not
// recorded when it should be.
//
exception = new AnalysisException("State set to ERROR without setting an exception");
}
} else {
exception = null;
}
}
/**
* Return a textual representation of the difference between the old entry and this entry. The
* difference is represented as a sequence of fields whose value would change if the old entry
* were converted into the new entry.
*
* @param oldEntry the entry being diff'd with this entry
* @return a textual representation of the difference
*/
public String getDiff(SourceEntry oldEntry) {
StringBuilder builder = new StringBuilder();
writeDiffOn(builder, oldEntry);
return builder.toString();
}
/**
* Return the exception that caused one or more values to have a state of {@link CacheState#ERROR}
* .
*
* @return the exception that caused one or more values to be uncomputable
*/
@Override
public AnalysisException getException() {
return exception;
}
/**
* Return {@code true} if the source was explicitly added to the context or {@code false} if the
* source was implicitly added because it was referenced by another source.
*
* @return {@code true} if the source was explicitly added to the context
*/
@Override
public boolean getExplicitlyAdded() {
return getFlag(EXPLICITLY_ADDED_FLAG);
}
@Override
public long getModificationTime() {
return modificationTime;
}
@Override
public CacheState getState(DataDescriptor<?> descriptor) {
if (descriptor == CONTENT) {
return contentState;
} else if (descriptor == LINE_INFO) {
return lineInfoState;
} else {
throw new IllegalArgumentException("Invalid descriptor: " + descriptor);
}
}
@Override
@SuppressWarnings("unchecked")
public <E> E getValue(DataDescriptor<E> descriptor) {
if (descriptor == CONTENT) {
return (E) content;
} else if (descriptor == LINE_INFO) {
return (E) lineInfo;
} else {
throw new IllegalArgumentException("Invalid descriptor: " + descriptor);
}
}
/**
* Invalidate all of the information associated with this source.
*/
public void invalidateAllInformation() {
content = null;
contentState = checkContentState(CacheState.INVALID);
lineInfo = null;
lineInfoState = CacheState.INVALID;
}
/**
* Record that an error occurred while attempting to get the contents of the source represented by
* this entry. This will set the state of all information, including any resolution-based
* information, as being in error.
*
* @param exception the exception that shows where the error occurred
*/
public void recordContentError(AnalysisException exception) {
content = null;
contentState = CacheState.ERROR;
recordScanError(exception);
}
/**
* Record that an error occurred while attempting to scan or parse the entry represented by this
* entry. This will set the state of all information, including any resolution-based information,
* as being in error.
*
* @param exception the exception that shows where the error occurred
*/
public void recordScanError(AnalysisException exception) {
setException(exception);
lineInfo = null;
lineInfoState = CacheState.ERROR;
}
/**
* Set whether the source was explicitly added to the context to match the given value.
*
* @param explicitlyAdded {@code true} if the source was explicitly added to the context
*/
public void setExplicitlyAdded(boolean explicitlyAdded) {
setFlag(EXPLICITLY_ADDED_FLAG, explicitlyAdded);
}
/**
* Set the most recent time at which the state of the source matched the state represented by this
* entry to the given time.
*
* @param time the new modification time of this entry
*/
public void setModificationTime(long time) {
modificationTime = time;
}
/**
* Set the state of the data represented by the given descriptor to the given state.
*
* @param descriptor the descriptor representing the data whose state is to be set
* @param the new state of the data represented by the given descriptor
*/
public void setState(DataDescriptor<?> descriptor, CacheState state) {
if (descriptor == CONTENT) {
content = updatedValue(state, content, null);
contentState = checkContentState(state);
} else if (descriptor == LINE_INFO) {
lineInfo = updatedValue(state, lineInfo, null);
lineInfoState = state;
} else {
throw new IllegalArgumentException("Invalid descriptor: " + descriptor);
}
}
/**
* Set the value of the data represented by the given descriptor to the given value.
*
* @param descriptor the descriptor representing the data whose value is to be set
* @param value the new value of the data represented by the given descriptor
*/
public <E> void setValue(DataDescriptor<E> descriptor, E value) {
if (descriptor == CONTENT) {
countTransitionToValid(descriptor, contentState);
content = (CharSequence) value;
contentState = checkContentState(CacheState.VALID);
} else if (descriptor == LINE_INFO) {
countTransitionToValid(descriptor, lineInfoState);
lineInfo = (LineInfo) value;
lineInfoState = CacheState.VALID;
} else {
throw new IllegalArgumentException("Invalid descriptor: " + descriptor);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
writeOn(builder);
return builder.toString();
}
/**
* Set the value of all of the flags with the given indexes to false.
*
* @param indexes the indexes of the flags whose value is to be set to false
*/
protected void clearFlags(int... indexes) {
for (int i = 0; i < indexes.length; i++) {
flags = BooleanArray.set(flags, indexes[i], false);
}
}
/**
* Copy the information from the given cache entry.
*
* @param entry the cache entry from which information will be copied
*/
protected void copyFrom(SourceEntryImpl entry) {
modificationTime = entry.modificationTime;
flags = entry.flags;
exception = entry.exception;
contentState = entry.contentState;
content = entry.content;
lineInfoState = entry.lineInfoState;
lineInfo = entry.lineInfo;
}
/**
* Increment count of transitions of the given {@link DataDescriptor} from the current
* {@link CacheState} to the {@link CacheState#VALID}.
*/
protected <E> void countTransitionToValid(DataDescriptor<E> descriptor, CacheState currentState) {
Map<CacheState, Integer> descriptorMap = transitionMap.get(descriptor);
if (descriptorMap == null) {
descriptorMap = Maps.newHashMap();
transitionMap.put(descriptor, descriptorMap);
}
Integer count = descriptorMap.get(currentState);
descriptorMap.put(currentState, count == null ? 1 : count.intValue() + 1);
}
/**
* Return the value of the flag with the given index.
*
* @param index the index of the flag whose value is to be returned
* @return the value of the flag with the given index
*/
protected boolean getFlag(int index) {
return BooleanArray.get(flags, index);
}
/**
* Return {@code true} if the state of any data value is {@link CacheState#ERROR}.
*
* @return {@code true} if the state of any data value is {@link CacheState#ERROR}
*/
protected boolean hasErrorState() {
return contentState == CacheState.ERROR || lineInfoState == CacheState.ERROR;
}
/**
* Set the exception that caused one or more values to have a state of {@link CacheState#ERROR} to
* the given exception.
*
* @param exception the exception that caused one or more values to be uncomputable
*/
protected void setException(AnalysisException exception) {
if (exception == null) {
throw new IllegalArgumentException("exception cannot be null");
}
this.exception = exception;
}
/**
* Set the value of the flag with the given index to the given value.
*
* @param index the index of the flag whose value is to be returned
* @param value the value of the flag with the given index
*/
protected void setFlag(int index, boolean value) {
flags = BooleanArray.set(flags, index, value);
}
/**
* Given that some data is being transitioned to the given state, return the value that should be
* kept in the cache.
*
* @param state the state to which the data is being transitioned
* @param currentValue the value of the data before the transition
* @param defaultValue the value to be used if the current value is to be removed from the cache
* @return the value of the data that should be kept in the cache
*/
protected <E> E updatedValue(CacheState state, E currentValue, E defaultValue) {
if (state == CacheState.VALID) {
throw new IllegalArgumentException("Use setValue() to set the state to VALID");
} else if (state == CacheState.IN_PROCESS) {
//
// We can leave the current value in the cache for any 'get' methods to access.
//
return currentValue;
}
return defaultValue;
}
/**
* Write a textual representation of the difference between the old entry and this entry to the
* given string builder.
*
* @param builder the string builder to which the difference is to be written
* @param oldEntry the entry that was replaced by this entry
* @return {@code true} if some difference was written
*/
protected boolean writeDiffOn(StringBuilder builder, SourceEntry oldEntry) {
boolean needsSeparator = false;
AnalysisException oldException = oldEntry.getException();
if (oldException != exception) {
builder.append("exception = ");
builder.append(oldException.getClass());
builder.append(" -> ");
builder.append(exception.getClass());
needsSeparator = true;
}
long oldModificationTime = oldEntry.getModificationTime();
if (oldModificationTime != modificationTime) {
if (needsSeparator) {
builder.append("; ");
}
builder.append("time = ");
builder.append(oldModificationTime);
builder.append(" -> ");
builder.append(modificationTime);
needsSeparator = true;
}
needsSeparator = writeStateDiffOn(builder, needsSeparator, oldEntry, CONTENT, "content");
needsSeparator = writeStateDiffOn(builder, needsSeparator, oldEntry, LINE_INFO, "lineInfo");
return needsSeparator;
}
/**
* Write a textual representation of this entry to the given builder. The result will only be used
* for debugging purposes.
*
* @param builder the builder to which the text should be written
*/
protected void writeOn(StringBuilder builder) {
builder.append("time = ");
builder.append(modificationTime);
builder.append("; content = ");
builder.append(contentState);
builder.append("; lineInfo = ");
builder.append(lineInfoState);
}
/**
* Write a textual representation of the difference between the state of the specified data
* between the old entry and this entry to the given string builder.
*
* @param builder the string builder to which the difference is to be written
* @param needsSeparator {@code true} if any data that is written
* @param oldEntry the entry that was replaced by this entry
* @param descriptor the descriptor defining the data whose state is being compared
* @param label the label used to describe the state
* @return {@code true} if some difference was written
*/
protected boolean writeStateDiffOn(StringBuilder builder, boolean needsSeparator,
SourceEntry oldEntry, DataDescriptor<?> descriptor, String label) {
CacheState oldState = oldEntry.getState(descriptor);
CacheState newState = getState(descriptor);
if (oldState != newState) {
if (needsSeparator) {
builder.append("; ");
}
builder.append(label);
builder.append(" = ");
builder.append(oldState);
builder.append(" -> ");
builder.append(newState);
return true;
}
return needsSeparator;
}
/**
* If the state is changing from ERROR to anything else, capture the information. This is an
* attempt to discover the underlying cause of a long-standing bug.
*
* @param newState the new state of the content
* @return the new state of the content
*/
private CacheState checkContentState(CacheState newState) {
if (contentState == CacheState.ERROR) {
InstrumentationBuilder builder = Instrumentation.builder("SourceEntryImpl-checkContentState");
builder.data("message", "contentState changing from " + contentState + " to " + newState);
//builder.data("source", source.getFullName());
builder.record(new AnalysisException());
builder.log();
}
return newState;
}
}