/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.apache.flex.compiler.internal.parsing.as; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import org.apache.flex.compiler.internal.scopes.ASFileScope; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; /** * Tokens generated from {@link StreamingASTokenizer} has been altered so that * their start and end offsets are absolute offsets. * <P> * This class can translate an "absolute" offset to a tuple of filename and * "local offset" in the file. Each {@link ASFileScope} has an * {@code OffsetLookup} object. */ public class OffsetLookup { /** * Comparator to sort {@code OffetCue} by absolute offset in ascending * order. */ private static final Comparator<OffsetCue> OFFSET_CUE_COMPARATOR = new Comparator<OffsetCue>() { @Override public int compare(OffsetCue o1, OffsetCue o2) { return o1.absolute - o2.absolute; } }; /** * This is used to sort the immutable list and do binary-search. */ private static final Ordering<OffsetCue> ORDER_BY_ABSOLUTE = Ordering.from(OFFSET_CUE_COMPARATOR); /** * Create a wrapper {@code OffsetCue} object in for binary-searching the * {@link #offsetCueList} * * @param absolute absolute offset * @return The {@code OffsetCue} that applies to the given absolute offset. */ private static OffsetCue createSearchKey(int absolute) { return new OffsetCue("", absolute, 0); } public OffsetLookup(final ImmutableList<OffsetCue> offsetCueList) { assert offsetCueList != null : "Offset cue list can't be null."; assert ORDER_BY_ABSOLUTE.isOrdered(offsetCueList) : "Offset cue list should be sorted by absolute offset."; // For now the creation of offsetCueList guarantees the ordering of the // list, so we needn't do a sorted immutable copy. this.offsetCueList = offsetCueList; } private final ImmutableList<OffsetCue> offsetCueList; /** * Find the nearest {@link OffsetCue} before the given absolute offset * * @param absoluteOffset absolute offset * @return The {@code OffsetCue} that applies to the given absolute offset. */ private OffsetCue findOffsetCue(int absoluteOffset) { if (offsetCueList.isEmpty() || absoluteOffset < 0) return null; final OffsetCue key = createSearchKey(absoluteOffset); final int index = ORDER_BY_ABSOLUTE.binarySearch(offsetCueList, key); // The return value of "binarySearch" is either the index of the found // item, or (-(insertion point) - 1). if (index >= 0) { return offsetCueList.get(index); } else { final int insertionPoint = -(index + 1); return offsetCueList.get(insertionPoint - 1); } } /** * Get the name of the file that contains the given absolute offset. * * @param absoluteOffset absolute offset * @return Name of the file that contains the given absolute offset. */ public String getFilename(final int absoluteOffset) { final OffsetCue result = findOffsetCue(absoluteOffset); if (result == null) return null; else return result.filename; } /** * Translate absolute offset to local offset. * * @param absoluteOffset absolute offset * @return Local offset. */ public int getLocalOffset(final int absoluteOffset) { final OffsetCue result = findOffsetCue(absoluteOffset); if (result == null) return absoluteOffset; else return absoluteOffset - result.adjustment; } /** * Convert a local offset to an absolute offset. If a file is included more * than once, there would be more than one absolute offset corresponding to * a given local offset within that included file. * * @param filename Normalized path of the file the local offset is in. * @param localOffset Local offset. * @return Absolute offsets corresponding to the given local offset. */ public int[] getAbsoluteOffset(final String filename, final int localOffset) { if (offsetCueList.isEmpty()) { return new int[] {localOffset}; } assert filename != null : "Filename can't be null."; if (localOffset < 0) return new int[] {localOffset}; // OffsetCue objects in the given file. final Iterable<OffsetCue> fileOffsetCueList = Iterables.filter(offsetCueList, new Predicate<OffsetCue>() { @Override public boolean apply(OffsetCue cue) { return cue.filename.equals(filename); } }); // Find a list of OffsetCues before the local offset. final List<OffsetCue> candidateCues = new ArrayList<OffsetCue>(1); OffsetCue candidate = null; for (final OffsetCue cue : fileOffsetCueList) { if (cue.local <= localOffset) { candidate = cue; } else if (candidate != null) { candidateCues.add(candidate); candidate = null; } } // There is no OffsetCue to end and included file. // The last one needs to be manually added. if (candidate != null) candidateCues.add(candidate); if (candidateCues.isEmpty()) { throw new IllegalArgumentException( "Local offset '" + localOffset + "' is not in file " + filename); } // Collect the absolute offsets. final int matchSize = candidateCues.size(); final int[] absoluteOffsets = new int[matchSize]; for (int i = 0; i < matchSize; i++) { final OffsetCue cue = candidateCues.get(i); absoluteOffsets[i] = cue.absolute + (localOffset - cue.local); } return absoluteOffsets; } @Override public String toString() { return Joiner.on("\n").join(offsetCueList); } /** * Determines if there are any includes. * * @return true if there are any included files, false otherwise. */ public boolean hasIncludes() { // If there are no Cue's then there are no includes. // If there is only one Cue, then there are no includes. return offsetCueList.size() > 1; } }