/******************************************************************************* * Copyright © 2011, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.debug.core.java; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IStorage; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.ISourceLocator; import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage; import org.eclipse.debug.core.sourcelookup.containers.ZipEntryStorage; import org.eclipse.edt.debug.core.EDTDebugCorePlugin; import org.eclipse.edt.debug.core.IEGLDebugCoreConstants; import org.eclipse.edt.debug.internal.core.java.SMAPLineParser; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.debug.core.IJavaReferenceType; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaType; import org.eclipse.jdt.debug.core.IJavaValue; import org.eclipse.jdt.debug.core.IJavaVariable; import org.eclipse.jdt.internal.debug.core.model.JDIReferenceType; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.ReferenceType; import com.sun.jdi.Type; /** * Utility class for handling SMAP data. */ @SuppressWarnings("restriction") public class SMAPUtil { private static final SMAPVariableInfo[] EMPTY_VARIABLE_INFOS = {}; private SMAPUtil() { // No instances. } /** * The SMAP will contain variable information after the normal end of the SMAP ("*E"). Any entries that are malformed will be ignored. * * @param smap The SMAP retrieved from the class. * @param frame The EGL-wrapped stack frame, if we are parsing variables for a stack frame. Pass in null if the SMAP is for a value instead. If we * find function information in the SMAP corresponding to the frame, the information will be passed to the frame. * @return an array of {@link SMAPVariableInfo}s, never null. */ public static SMAPVariableInfo[] parseVariables( String smap, IEGLJavaStackFrame frame ) { if ( smap == null || smap.trim().length() == 0 ) { return EMPTY_VARIABLE_INFOS; } List<SMAPVariableInfo> vars = new ArrayList<SMAPVariableInfo>(); // Skip ahead to the variable section. int idx = smap.indexOf( "*E" ); //$NON-NLS-1$ if ( idx != -1 ) { String variableSection = smap.substring( idx + 2 ).trim(); StringTokenizer tok = new StringTokenizer( variableSection, "\n" ); //$NON-NLS-1$ String javaFrameSignature = null; if ( frame != null ) { try { javaFrameSignature = frame.getJavaStackFrame().getMethodName() + ";" + frame.getJavaStackFrame().getSignature(); //$NON-NLS-1$ //$NON-NLS-2$ } catch ( DebugException de ) { } } String currentFunction = null; StringTokenizer semiTok; int pound; int semicolon; int tokenLen; int line; boolean specialGlobalType; while ( tok.hasMoreTokens() ) { // If we encounter anything unexpected, skip the entry. Shouldn't be a problem so long // as no one's mucking around with the SMAP. String next = tok.nextToken().trim(); tokenLen = next.length(); if ( tokenLen == 0 ) { continue; } if ( "*X".equals( next ) ) //$NON-NLS-1$ { // We're done processing. break; } specialGlobalType = next.charAt( 0 ) == '*'; try { if ( specialGlobalType ) { line = -1; semicolon = next.indexOf( ';' ); } else { pound = next.indexOf( '#' ); if ( pound == -1 ) { continue; } line = Integer.parseInt( next.substring( 0, pound ) ); semicolon = next.indexOf( ';', pound ); } if ( semicolon == -1 ) { continue; } if ( specialGlobalType ) { // It's a global type that doesn't have a specific line (library, table, form, program parameter). semiTok = new StringTokenizer( next.substring( semicolon + 1 ), ";" ); //$NON-NLS-1$ int tokenCount = semiTok.countTokens(); if ( tokenCount > 0 ) { String eglName; String javaName; String type; // Required token: egl name eglName = semiTok.nextToken(); // Next token: java name (defaults to egl name) if ( tokenCount > 1 ) { javaName = semiTok.nextToken(); } else { javaName = eglName; } // Next token: egl type (defaults to egl name) if ( tokenCount > 2 ) { type = semiTok.nextToken(); } else { type = eglName; } vars.add( new SMAPVariableInfo( eglName, javaName, type, line, next ) ); } } else if ( tokenLen > semicolon + 2 && next.charAt( semicolon + 1 ) == 'F' && next.charAt( semicolon + 2 ) == ':' ) { // It's a function line. First portion is the egl name, second is the java signature. int semicolon2 = next.indexOf( ';', semicolon + 1 ); if ( semicolon2 != -1 ) { String eglName = next.substring( semicolon + 3, semicolon2 ); currentFunction = next.substring( semicolon2 + 1 ); if ( frame != null && currentFunction != null && currentFunction.equals( javaFrameSignature ) ) { frame.setSMAPFunctionInfo( new SMAPFunctionInfo( eglName, currentFunction, line, next ) ); } } } else { // It's a variable line. semiTok = new StringTokenizer( next.substring( semicolon + 1 ), ";" ); //$NON-NLS-1$ if ( semiTok.countTokens() == 3 ) { vars.add( new SMAPVariableInfo( semiTok.nextToken(), semiTok.nextToken(), semiTok.nextToken(), line, currentFunction, next ) ); } } } catch ( NumberFormatException nfe ) { // Ignore the entry, but log the error. EDTDebugCorePlugin.log( nfe ); } } } return vars.toArray( new SMAPVariableInfo[ vars.size() ] ); } /** * Returns the SMAP information for the Java type. It will never be null. If there is no SMAP information, or the Java type was not a type that we * recognize (currently only JDIReferenceType), then this will return blank. When the class file doesn't contain SMAP information we'll try to * read in *.eglsmap files from the disk. * * @param target The EGL debug target. * @param type The Java type. * @return the SMAP information. */ public static String getSMAP( IEGLJavaDebugTarget target, IJavaType type ) { String smap = null; if ( type instanceof IJavaReferenceType ) { if ( target.supportsSourceDebugExtension() && type instanceof JDIReferenceType ) { Type underlyingType = ((JDIReferenceType)type).getUnderlyingType(); if ( underlyingType instanceof ReferenceType ) { try { smap = ((ReferenceType)underlyingType).sourceDebugExtension(); } catch ( AbsentInformationException e ) { } catch ( UnsupportedOperationException e ) { } } } if ( smap == null ) { // No SMAP in the class file; check if the *.eglsmap file can be read in directly. try { smap = getSMAP( target, ((IJavaReferenceType)type).getName() ); } catch ( Throwable t ) { // Ignore. } } } return smap == null ? "" : smap.trim(); //$NON-NLS-1$ } /** * Returns the SMAP information for the class name by reading the file off the disk. If there is no SMAP information this will return blank. * * @param target The EGL debug target. * @param className The qualified name of the class. * @return the SMAP information. */ public static String getSMAP( IEGLJavaDebugTarget target, String className ) { SMAPFileCache smapFileCache = target.getSMAPFileCache(); if ( smapFileCache == null ) { return ""; //$NON-NLS-1$ } String smap = null; if ( smapFileCache.containsSMAP( className ) ) { smap = smapFileCache.getSMAP( className ); } else { // Remove inner classes since we're looking for OuterClass.eglsmap. int inner = className.indexOf( '$' ); if ( inner != -1 ) { className = className.substring( 0, inner ); } // Try finding it from the user's workspace, including classpath entries like jars, runtime containers, etc. String workspacePath = null; ISourceLocator locator = target.getLaunch().getSourceLocator(); if ( locator instanceof ISourceLookupDirector ) { InputStream is = null; Object src = ((ISourceLookupDirector)locator).getSourceElement( className.replace( '.', '/' ) + ".java" ); //$NON-NLS-1$ if ( src instanceof IJavaElement ) { IJavaElement parent = ((IJavaElement)src).getParent(); if ( parent instanceof IPackageFragment ) { String simpleSMAPName; int lastDot = className.lastIndexOf( '.' ); if ( lastDot == -1 ) { simpleSMAPName = className + "." + IEGLDebugCoreConstants.SMAP_EXTENSION; //$NON-NLS-1$ } else { simpleSMAPName = className.substring( lastDot + 1 ) + "." + IEGLDebugCoreConstants.SMAP_EXTENSION; //$NON-NLS-1$ } try { Object[] kids = ((IPackageFragment)parent).getNonJavaResources(); for ( Object kid : kids ) { if ( kid instanceof IStorage && ((IStorage)kid).getName().equals( simpleSMAPName ) ) { is = ((IStorage)kid).getContents(); workspacePath = ((IStorage)kid).getFullPath().toString(); break; } } } catch ( CoreException ce ) { EDTDebugCorePlugin.log( ce ); } } } else if ( src instanceof ZipEntryStorage ) { ZipEntryStorage storage = (ZipEntryStorage)src; ZipFile zipFile = storage.getArchive(); ZipEntry zipEntry = storage.getZipEntry(); String name = zipEntry.getName(); if ( name.endsWith( ".java" ) ) //$NON-NLS-1$ { zipEntry = zipFile.getEntry( name.substring( 0, name.length() - 4 ) + IEGLDebugCoreConstants.SMAP_EXTENSION ); if ( zipEntry != null ) { try { is = zipFile.getInputStream( zipEntry ); } catch ( IOException ioe ) { EDTDebugCorePlugin.log( ioe ); } } } } else if ( src instanceof LocalFileStorage ) { // Filesystem-absolute path. IPath smapPath = ((IStorage)src).getFullPath().removeFileExtension().addFileExtension( IEGLDebugCoreConstants.SMAP_EXTENSION ); File smapFile = smapPath.toFile(); if ( smapFile.exists() ) { try { is = new FileInputStream( smapFile ); IPath workspaceRoot = ResourcesPlugin.getWorkspace().getRoot().getLocation(); if ( workspaceRoot.isPrefixOf( smapPath ) ) { workspacePath = "/" + smapPath.removeFirstSegments( workspaceRoot.segmentCount() ).toString(); //$NON-NLS-1$ } } catch ( FileNotFoundException fnfe ) { EDTDebugCorePlugin.log( fnfe ); } } } else if ( src instanceof IStorage ) { // This will only work if it's inside the workspace. IPath smapPath = ((IStorage)src).getFullPath().removeFileExtension().addFileExtension( IEGLDebugCoreConstants.SMAP_EXTENSION ); IFile smapFile = ResourcesPlugin.getWorkspace().getRoot().getFile( smapPath ); if ( smapFile.exists() ) { try { is = smapFile.getContents( true ); workspacePath = smapPath.toString(); } catch ( CoreException ce ) { EDTDebugCorePlugin.log( ce ); } } } if ( is != null ) { try { BufferedInputStream bis = new BufferedInputStream( is ); byte[] b = new byte[ bis.available() ]; bis.read( b, 0, b.length ); smap = new String( b, "UTF-8" ); //$NON-NLS-1$ } catch ( Exception e ) { EDTDebugCorePlugin.log( e ); } finally { try { is.close(); } catch ( IOException ioe ) { } } } } smapFileCache.addEntry( className, smap, workspacePath ); } return smap == null ? "" //$NON-NLS-1$ : smap; } /** * @return true if the given value's default stratum equals {@link IEGLDebugCoreConstants#EGL_STRATUM} */ public static boolean isEGLStratum( IJavaValue value ) { try { IJavaType type = value.getJavaType(); return type instanceof IJavaReferenceType && IEGLDebugCoreConstants.EGL_STRATUM.equals( ((IJavaReferenceType)type).getDefaultStratum() ); } catch ( DebugException e ) { } return false; } /** * @return true if the given variable's default stratum equals {@link IEGLDebugCoreConstants#EGL_STRATUM} */ public static boolean isEGLStratum( IJavaVariable variable ) { try { IJavaType type = variable.getJavaType(); return type instanceof IJavaReferenceType && IEGLDebugCoreConstants.EGL_STRATUM.equals( ((IJavaReferenceType)type).getDefaultStratum() ); } catch ( DebugException e ) { } return false; } /** * @return true if the given frame's default stratum equals {@link IEGLDebugCoreConstants#EGL_STRATUM} */ public static boolean isEGLStratum( IJavaStackFrame frame, IEGLJavaDebugTarget target ) { if ( target.supportsSourceDebugExtension() ) { try { IJavaReferenceType refType = frame.getReferenceType(); return refType != null && IEGLDebugCoreConstants.EGL_STRATUM.equals( refType.getDefaultStratum() ); } catch ( DebugException e ) { } } else { try { String smap = SMAPUtil.getSMAP( target, frame.getReferenceType().getName() ); return SMAPUtil.isEGLStratum( smap ); } catch ( DebugException e ) { } } return false; } /** * @return true if the smap value has a default stratum of {@link IEGLDebugCoreConstants#EGL_STRATUM} */ public static boolean isEGLStratum( String smap ) { if ( smap.length() > 0 ) { // Third line has the default stratum. int index = smap.indexOf( '\n' ); if ( index != -1 ) { index = smap.indexOf( '\n', index + 1 ); if ( index != -1 ) { int end = smap.indexOf( '\n', index + 1 ); if ( end != -1 ) { return IEGLDebugCoreConstants.EGL_STRATUM.equals( smap.substring( index + 1, end ) ); } } } } return false; } /** * Given SMAP data, this returns the full path of the source file, or if that's not available it returns the file name. If the SMAP data is not * valid then this returns null. * * @param smap The SMAP data. * @return the path to the source file, possibly null. */ public static String getFilePath( String smap ) { if ( smap.length() > 0 ) { int index = smap.indexOf( "*F" ); //$NON-NLS-1$ if ( index != -1 ) { // Skip this line and the next, to get the full path. index = smap.indexOf( '\n', index ); if ( index != -1 ) { // If the next line starts with '+' then the following line has the full path. // Otherwise no full path was given and all we know is the file name. if ( smap.charAt( index + 1 ) == '+' ) { index = smap.indexOf( '\n', index + 1 ); if ( index != -1 ) { int end = smap.indexOf( '\n', index + 1 ); if ( end != -1 ) { return smap.substring( index + 1, end ); } } } else { index = smap.indexOf( ' ', index + 1 ); if ( index != -1 ) { int end = smap.indexOf( '\n', index + 1 ); if ( end != -1 ) { return smap.substring( index + 1, end ).trim(); // trim() because it could have been multiple whitespace chars } } } } } } return null; } /** * Given SMAP data, this returns the name of the source file. * * @param smap The SMAP data. * @return the SMAP's source file name, possibly null. */ public static String getFileName( String smap ) { String path = getFilePath( smap ); if ( path != null ) { int index = path.lastIndexOf( '/' ); if ( index == -1 ) { return path; } return path.substring( index + 1 ); } return null; } /** * Given SMAP data, this returns the parsed line information which maps Java and EGL lines. This should only be used when the target VM does not * support JSR-45. If the line mappings were not already parsed, that will be done at this time. * * @param smap The SMAP data. * @param lineMappingCache The line mapping cache, whose key is the SMAP data. * @return the parsed line mappings, possibly null. */ public static SMAPLineInfo getSMAPLineInfo( String smap, Map<String, SMAPLineInfo> lineMappingCache ) { if ( lineMappingCache != null && smap.length() > 0 ) { SMAPLineInfo info; if ( !lineMappingCache.containsKey( smap ) ) { info = SMAPLineParser.parse( smap ); lineMappingCache.put( smap, info ); } else { info = lineMappingCache.get( smap ); } return info; } return null; } }