/* * 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; } }