| /* |
| * 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 flex.tools.debugger.cli; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| |
| import flash.tools.debugger.InProgressException; |
| import flash.tools.debugger.Isolate; |
| import flash.tools.debugger.NoResponseException; |
| import flash.tools.debugger.Session; |
| import flash.tools.debugger.SourceFile; |
| import flash.tools.debugger.SwfInfo; |
| import flash.util.IntMap; |
| |
| /** |
| * FileInfoCache manages a list of files that are unique |
| * across multiple swfs. |
| */ |
| public class FileInfoCache implements Comparator<SourceFile> |
| { |
| Session m_session; |
| |
| /** |
| * We can get at files by module id or path |
| */ |
| IntMap m_byInt = new IntMap(); |
| HashMap<Integer, IntMap> m_isolateState = new HashMap<Integer, IntMap> (); |
| |
| private IntMap getIsolateState(int isolateId) { |
| IntMap isolateState = null; |
| if (!m_isolateState.containsKey(isolateId)) { |
| isolateState = new IntMap(); |
| m_isolateState.put(isolateId, isolateState); |
| } |
| else |
| isolateState = m_isolateState.get(isolateId); |
| return isolateState; |
| } |
| |
| SourceFile[] m_files = null; |
| SourceFile[] m_isolateFiles = null; |
| SwfInfo m_swfFilter = null; |
| int m_swfsLoaded = 0; |
| boolean m_dirty = false; |
| int lastActiveIsolate = Isolate.DEFAULT_ID; |
| |
| public FileInfoCache() { |
| |
| } |
| |
| public void bind(Session s) { setSession(s); } |
| public void unbind() { m_session = null; } |
| |
| public SourceFile getFile(int i) { |
| return getFile(i, Isolate.DEFAULT_ID); |
| } |
| |
| public SourceFile getFile(int i, int isolateId) { |
| populate(); |
| if (isolateId == Isolate.DEFAULT_ID) |
| return (SourceFile) m_byInt.get(i); |
| else |
| return (SourceFile)getIsolateState(isolateId).get(i); |
| } |
| |
| public SourceFile[] getFileList() { |
| populate(); |
| return m_files; |
| } |
| |
| public SourceFile[] getFileList(int isolateId) { |
| populate(); |
| if (isolateId == Isolate.DEFAULT_ID) |
| return m_files; |
| else if (isolateId != lastActiveIsolate) { |
| buildIsolateFiles(isolateId); |
| } |
| return m_isolateFiles; |
| } |
| |
| private void buildIsolateFiles(int isolateId) { |
| SwfInfo[] swfs = getSwfs(isolateId); |
| boolean worked = true; // check that all worked correctly |
| ArrayList<SourceFile> files = new ArrayList<SourceFile>(); |
| |
| for(int i=0; i<swfs.length; i++) |
| { |
| if (swfs[i] != null) |
| worked = loadSwfFiles(files, swfs[i]) ? worked : false; |
| } |
| |
| // trim the file list |
| ArrayList<SourceFile> fa = trimFileList(files); |
| m_isolateFiles = fa.toArray( new SourceFile[fa.size()] ); |
| |
| // sort this array in place so calls to getFileList will be ordered |
| Arrays.sort(m_isolateFiles, this); |
| } |
| |
| public Iterator getAllFiles(int isolateId) { |
| populate(); |
| if (isolateId == Isolate.DEFAULT_ID) |
| return m_byInt.iterator(); |
| else |
| return getIsolateState(isolateId).iterator(); |
| } |
| |
| public SwfInfo getSwfFilter() { return m_swfFilter; } |
| public boolean isSwfFilterOn() { return (m_swfFilter != null); } |
| public void setDirty() { m_dirty = true; } |
| |
| void setSession(Session s) |
| { |
| m_session = s; |
| m_swfFilter = null; |
| clear(); |
| } |
| |
| SwfInfo[] getAllSwfs() { |
| ArrayList<SwfInfo> result = new ArrayList<SwfInfo>(); |
| |
| for ( Isolate isolate : m_session.getWorkers()) { |
| SwfInfo[] swfs = new SwfInfo[0]; |
| try { |
| swfs = m_session.getWorkerSession(isolate.getId()).getSwfs(); |
| } catch (NoResponseException e) { |
| swfs = new SwfInfo[0]; |
| } |
| |
| for (SwfInfo swf : swfs) |
| result.add(swf); |
| } |
| |
| return result.toArray(new SwfInfo[0]); |
| } |
| |
| void populate() |
| { |
| // do we have a new swf to load? |
| if (m_session != null && (m_dirty || getAllSwfs().length > m_swfsLoaded)) |
| reloadCache(); |
| } |
| |
| void reloadCache() |
| { |
| clear(); |
| loadCache(); |
| m_dirty = false; |
| } |
| |
| void clear() |
| { |
| m_byInt.clear(); |
| m_isolateState.clear(); |
| m_files = null; |
| } |
| |
| /** |
| * Determine if the given SourceFile is in the current fileList |
| */ |
| public boolean inFileList(SourceFile f) |
| { |
| boolean isIt = false; |
| |
| SourceFile[] files = getFileList(); |
| for(int i=0; i<files.length && !isIt; i++) |
| { |
| if (files[i] == f) |
| isIt = true; |
| } |
| return isIt; |
| } |
| |
| /** |
| * Go out to the session and request a list of files |
| * But we dump ones that have a name collision. |
| * Also if selectedSwf is set then we only add files |
| * that are contained within the given swf. |
| */ |
| void loadCache() |
| { |
| boolean worked = true; // check that all worked correctly |
| ArrayList<SourceFile> files = new ArrayList<SourceFile>(); |
| SwfInfo[] swfs = getAllSwfs(); |
| for(int i=0; i<swfs.length; i++) |
| { |
| if (swfs[i] != null) |
| worked = loadSwfFiles(files, swfs[i]) ? worked : false; |
| } |
| |
| // trim the file list |
| ArrayList<SourceFile> fa = trimFileList(files); |
| m_files = fa.toArray( new SourceFile[fa.size()] ); |
| |
| // sort this array in place so calls to getFileList will be ordered |
| Arrays.sort(m_files, this); |
| |
| // mark our cache complete if all was good. |
| if (worked) |
| m_swfsLoaded = swfs.length; |
| } |
| |
| boolean loadSwfFiles(ArrayList<SourceFile> ar, SwfInfo swf) |
| { |
| boolean worked = true; |
| try |
| { |
| // @todo should we include unloaded swfs? |
| SourceFile[] files = swf.getSourceList(m_session); |
| ar.ensureCapacity(ar.size()+files.length); |
| |
| // add each file to our global source file IntMap and our list |
| for(int i=0; i<files.length; i++) |
| { |
| putFile(files[i], swf.getIsolateId()); |
| ar.add(files[i]); |
| } |
| } |
| catch(InProgressException ipe) |
| { |
| // can't load this one, its not ready yet |
| worked = false; |
| } |
| return worked; |
| } |
| |
| /** |
| * Walk the file list looking for name collisions. |
| * If we find one, then we remove it |
| */ |
| ArrayList<SourceFile> trimFileList(ArrayList<SourceFile> files) |
| { |
| HashMap<String, String> names = new HashMap<String, String>(); |
| ArrayList<SourceFile> list = new ArrayList<SourceFile>(); |
| |
| int size = files.size(); |
| for(int i=0; i<size; i++) |
| { |
| boolean addIt = false; |
| |
| SourceFile fi = files.get(i); |
| // no filter currently in place so we add the file as long |
| // as no duplicates exist. We use the original Swd full |
| // name for matching. |
| String fName = fi.getRawName(); |
| if (m_swfFilter == null) |
| { |
| // If it exists, then we don't add it! |
| if (names.get(fName) == null) |
| addIt = true; |
| } |
| else |
| { |
| // we have a filter in place so, see |
| // if the source file is in our currently |
| // selected swf. |
| addIt = m_swfFilter.containsSource(fi); |
| } |
| |
| // did we mark this one to add? |
| if (addIt) |
| { |
| names.put(fName, fName); |
| list.add(fi); |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * All files from all swfs are placed into our byInt map |
| * since we know that ids are unique across the entire |
| * Player session. |
| * |
| * This is also important in the case that the player |
| * halts in one of these files, that the debugger |
| * be able to locate the SourceFile so that we can |
| * display the correct context for the user. |
| */ |
| void putFile(SourceFile s, int isolateId) |
| { |
| int i = s.getId(); |
| if (isolateId == Isolate.DEFAULT_ID) |
| m_byInt.put(i, s); |
| else |
| getIsolateState(isolateId).put(i, s); |
| } |
| |
| /** |
| * Attempt to set a swf as a filter |
| * for the file list that we create |
| */ |
| public boolean setSwfFilter(String swfName) |
| { |
| // look for a match in our list |
| boolean worked = false; |
| if (swfName == null) |
| { |
| m_swfFilter = null; |
| worked = true; |
| } |
| else |
| { |
| SwfInfo[] swfs = getAllSwfs(); |
| for(int i=0; i<swfs.length; i++) |
| { |
| SwfInfo e = swfs[i]; |
| if (e != null && nameOfSwf(e).equalsIgnoreCase(swfName)) |
| { |
| worked = true; |
| m_swfFilter = e; |
| break; |
| } |
| } |
| } |
| |
| // reload if it worked |
| if (worked) |
| reloadCache(); |
| |
| return worked; |
| } |
| |
| // list all swfs we know about |
| public SwfInfo[] getSwfs(int isolateId) |
| { |
| return getSwfsIsolate(isolateId); |
| } |
| |
| public SwfInfo[] getSwfsIsolate(int isolateId) |
| { |
| SwfInfo[] swfs = null; |
| try |
| { |
| swfs = m_session.getWorkerSession(isolateId).getSwfs(); |
| } |
| catch(NoResponseException nre) |
| { |
| swfs = new SwfInfo[] {}; // oh bery bad |
| } |
| return swfs; |
| } |
| |
| /** |
| * Given a SourceFile locate the swf which it came from |
| */ |
| public SwfInfo swfForFile(SourceFile f, int isolateId) |
| { |
| // We use the id to determine which swf this source files resides in |
| int id = f.getId(); |
| SwfInfo info = null; |
| SwfInfo[] swfs = getSwfs(isolateId);//getAllSwfs(); |
| for(int i=0; ( i<swfs.length && (info == null) ); i++) |
| { |
| if (swfs[i] != null && swfs[i].containsSource(f)) |
| info = swfs[i]; |
| } |
| return info; |
| } |
| |
| // locate the name of the swf |
| public static String nameOfSwf(SwfInfo e) |
| { |
| int at = -1; |
| String name = e.getUrl(); |
| if ( (at = e.getUrl().lastIndexOf('/')) > -1) |
| name = e.getUrl().substring(at+1); |
| if ( (at = e.getUrl().lastIndexOf('\\')) > -1) |
| name = e.getUrl().substring(at+1); |
| else if ( (at = e.getPath().lastIndexOf('\\')) > -1) |
| name = e.getPath().substring(at+1); |
| else if ( (at = e.getPath().lastIndexOf('/')) > -1) |
| name = e.getPath().substring(at+1); |
| |
| // now rip off any trailing ? options |
| at = name.lastIndexOf('?'); |
| name = (at > -1) ? name.substring(0, at) : name; |
| |
| return name; |
| } |
| |
| // locate the name of the swf |
| public static String shortNameOfSwf(SwfInfo e) |
| { |
| String name = nameOfSwf(e); |
| |
| // now strip off any leading path |
| int at = -1; |
| if ( (at = name.lastIndexOf('/')) > -1) |
| name = name.substring(at+1); |
| else if ( (at = name.lastIndexOf('\\')) > -1) |
| name = name.substring(at+1); |
| return name; |
| } |
| |
| /** |
| * Given the URL of a specfic swf determine |
| * if there is a file within it that appears |
| * to be the same as the given source file |
| * @param f |
| * @return |
| */ |
| public SourceFile similarFileInSwf(SwfInfo info, SourceFile f) throws InProgressException |
| { |
| SourceFile hit = null; |
| SourceFile[] files = info.getSourceList(m_session); |
| if (!info.isProcessingComplete()) |
| throw new InProgressException(); |
| |
| for(int i=0; i<files.length; i++) |
| { |
| if (filesMatch(f, files[i])) |
| hit = files[i]; |
| } |
| return hit; |
| } |
| |
| /** |
| * Comparator interface for sorting SourceFiles |
| */ |
| public int compare(SourceFile o1, SourceFile o2) |
| { |
| String n1 = o1.getName(); |
| String n2 = o2.getName(); |
| |
| return n1.compareTo(n2); |
| } |
| |
| /** |
| * Compare two files and determine if they are the same. |
| * Our criteria included only line count package names |
| * and the name of the class itself. If there are |
| * any other differences then we won't be able to detect |
| * them. We should probably do something like an MD5 |
| * computation on the characters in ScriptText. Then |
| * we'd really be sure of a match. |
| * @param a first file to compare |
| * @param b second file to compare |
| * @return true if files appear to be the same |
| */ |
| public boolean filesMatch(SourceFile a, SourceFile b) |
| { |
| boolean yes = true; |
| |
| if (a == null || b == null) |
| yes = false; |
| else if (a.getPackageName().compareTo(b.getPackageName()) != 0) |
| yes = false; |
| else if (a.getName().compareTo(b.getName()) != 0) |
| yes = false; |
| else if (a.getLineCount() != b.getLineCount()) // warning, this is sometimes expensive, so do it last |
| yes = false; |
| |
| return yes; |
| } |
| /** |
| * Return a array of SourceFiles whose names match |
| * the specified string. The array is sorted by name. |
| * The input can be mx.controls.xxx which will |
| */ |
| public SourceFile[] getFiles(String matchString) |
| { |
| boolean doStartsWith = false; |
| boolean doIndexOf = false; |
| boolean doEndsWith = false; |
| |
| boolean leadingAsterisk = matchString.startsWith("*") && matchString.length() > 1; //$NON-NLS-1$ |
| boolean trailingAsterisk = matchString.endsWith("*"); //$NON-NLS-1$ |
| boolean usePath = matchString.indexOf('.') > -1; |
| |
| if (leadingAsterisk && trailingAsterisk) |
| { |
| matchString = matchString.substring(1, matchString.length() - 1); |
| doIndexOf = true; |
| } |
| else if (leadingAsterisk) |
| { |
| matchString = matchString.substring(1); |
| doEndsWith = true; |
| } |
| else if (trailingAsterisk) |
| { |
| matchString = matchString.substring(0, matchString.length() - 1); |
| doStartsWith = true; |
| } |
| else if (usePath) |
| { |
| doIndexOf = true; |
| } |
| else |
| { |
| doStartsWith = true; |
| } |
| |
| SourceFile[] files = getFileList(); |
| ArrayList<SourceFile> fileList = new ArrayList<SourceFile>(); |
| int n = files.length; |
| int exactHitAt = -1; |
| // If the matchString already starts with "." (e.g. ".as" or ".mxml"), then dotMatchString |
| // will be equal to matchString; otherwise, dotMatchString will be "." + matchString |
| String dotMatchString = (matchString.startsWith(".")) ? matchString : ("." + matchString); //$NON-NLS-1$ //$NON-NLS-2$ |
| for (int i = 0; i < n; i++) |
| { |
| SourceFile sourceFile = files[i]; |
| boolean pathExists = (usePath && sourceFile.getFullPath().matches(".*[/\\\\].*")); //$NON-NLS-1$ |
| String name = pathExists ? sourceFile.getFullPath() : sourceFile.getName(); |
| |
| // if we are using the full path string, then prefix a '.' to our matching string so that abc.as and Gabc.as don't both hit |
| String match = (usePath && pathExists) ? dotMatchString : matchString; |
| |
| name = name.replace('/', '.'); // get rid of path identifiers and use dots |
| name = name.replace('\\', '.'); // would be better to modify the input string, but we don't know which path char will be used. |
| |
| // exact match? We are done |
| if (name.equals(match)) |
| { |
| exactHitAt = i; |
| break; |
| } |
| else if (doStartsWith && name.startsWith(match)) |
| fileList.add(sourceFile); |
| else if (doEndsWith && name.endsWith(match)) |
| fileList.add(sourceFile); |
| else if (doIndexOf && name.indexOf(match) > -1) |
| fileList.add(sourceFile); |
| } |
| |
| // trim all others if we have an exact file match |
| if (exactHitAt > -1) |
| { |
| fileList.clear(); |
| fileList.add(files[exactHitAt]); |
| } |
| |
| SourceFile[] fileArray = fileList.toArray( new SourceFile[fileList.size()] ); |
| Arrays.sort(fileArray, this); |
| return fileArray; |
| } |
| } |