/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed 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 de.codesourcery.jasm16.utils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.lang.reflect.Array; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.WordAddress; import de.codesourcery.jasm16.compiler.CompilationMarker; import de.codesourcery.jasm16.compiler.ICompilationUnit; import de.codesourcery.jasm16.compiler.IMarker; import de.codesourcery.jasm16.compiler.SourceLocation; import de.codesourcery.jasm16.compiler.io.IResource; import de.codesourcery.jasm16.disassembler.DisassembledLine; import de.codesourcery.jasm16.exceptions.NoDirectoryException; import de.codesourcery.jasm16.scanner.IScanner; import de.codesourcery.jasm16.scanner.Scanner; /** * Class with various utility methods (most are only used by unit-tests). * * @author tobias.gierke@code-sourcery.de */ public class Misc { private static final Logger LOG = Logger.getLogger(Misc.class); // code assumes these characters are lowercase !!! Do NOT change this private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); public static String toHexString(byte[] data) { final StringBuilder builder = new StringBuilder(); for ( int i = 0 ; i < data.length ; i++) { builder.append( "0x"+toHexString( data[i] ) ); if ( (i+1) < data.length ) { builder.append(","); } } return builder.toString(); } public static String toHexDumpWithAddresses(Address startingAddressInBytes , byte[] data, int wordsPerLine) { return toHexDumpWithAddresses( startingAddressInBytes , data , data.length , wordsPerLine ); } public static String toHexDumpWithAddresses(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine) { return toHexDumpWithAddresses(startingAddressInBytes, data, length, wordsPerLine, false ); } public static String toHexDumpWithoutAddresses(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine) { return toHexDump(startingAddressInBytes, data, length, wordsPerLine, false , false ); } public static String toHexDumpWithAddresses(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII) { return toHexDump(startingAddressInBytes, data, length, wordsPerLine, printASCII, true); } public static String toHexDumpWithAddresses(int startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII) { return toHexDump(startingAddressInBytes, data, length, wordsPerLine, printASCII, true , false ); } public static String toHexDumpWithAddresses(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII,boolean wrapAddress) { return toHexDump(startingAddressInBytes, data, length, wordsPerLine, printASCII, true,wrapAddress); } public static String toHexDump(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII,boolean printAddress) { return toHexDump( startingAddressInBytes, data , length , wordsPerLine , printASCII, printAddress , false ); } public static String toHexDump(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII,boolean printAddress,boolean wrapAddress) { return toHexDump( startingAddressInBytes.getByteAddressValue() ,data, length , wordsPerLine,printASCII, printAddress,wrapAddress); } public static String toHexDump(int startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII,boolean printAddress,boolean wrapAddress) { final List<String> lines = toHexDumpLines(startingAddressInBytes, data, length, wordsPerLine, printASCII, printAddress,wrapAddress); StringBuilder result = new StringBuilder(); for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) { String line = iterator.next(); result.append( line ); if ( iterator.hasNext() ) { result.append("\n"); } } return result.toString(); } public static List<String> toHexDumpLines(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII,boolean printAddress) { return toHexDumpLines(startingAddressInBytes, data, length, wordsPerLine, printASCII, printAddress, false ); } public static List<String> toHexDumpLines(Address startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII,boolean printAddress,boolean wrapAddress) { return toHexDumpLines(startingAddressInBytes.getByteAddressValue() , data, length , wordsPerLine, printASCII, printAddress, wrapAddress); } public static List<String> toHexDumpLines(int startingAddressInBytes , byte[] data, int length , int wordsPerLine,boolean printASCII,boolean printAddress,boolean wrapAddress) { final List<String> result = new ArrayList<String>(); final StringBuilder asciiBuilder = new StringBuilder(); final StringBuilder hexBuilder = new StringBuilder(); int current = 0; while( current < length ) { if ( printAddress ) { int wordAddress = (startingAddressInBytes+current) >> 1; if ( wrapAddress ) { wordAddress = (int) ( wordAddress % (WordAddress.MAX_ADDRESS+1) ); } hexBuilder.append( toHexString( wordAddress ) ).append(": "); } for ( int i = 0 ; current < length && i < wordsPerLine ; i++) { byte b1 = data[current++]; hexBuilder.append( toHexString( b1 ) ); if ( printASCII ) { asciiBuilder.append( toASCII(b1) ); } if ( current >= length ) { break; } b1 = data[current++]; hexBuilder.append( toHexString( b1 ) ); if ( printASCII ) { asciiBuilder.append( toASCII(b1) ); } if ( current >= length ) { break; } hexBuilder.append(" "); } if ( printASCII ) { hexBuilder.append(" ").append( asciiBuilder.toString() ); asciiBuilder.setLength( 0 ); } result.add( hexBuilder.toString() ); hexBuilder.setLength( 0 ); asciiBuilder.setLength( 0 ); } if ( printASCII && asciiBuilder.length() > 0 ) { hexBuilder.append(" ").append( asciiBuilder.toString() ); } if ( hexBuilder.length() > 0 ) { result.add( hexBuilder.toString() ); } return result; } private static char toASCII(byte b) { int val = b; if ( val < 0 ) { val+=256; } if ( val < 32 || val > 126 ) { return '.'; } return (char) val; } public static String toHexString(Address address) { return toHexString( address.getValue() ); } public static String toHexString(int val) { if ( ( val & 0xff000000 ) != 0 ) { return toHexString( (byte) ( (val >>> 24 ) & 0x00ff ) )+ toHexString( (byte) ( (val >>> 16 ) & 0x00ff ) )+ toHexString( (byte) ( (val >>> 8 ) & 0x00ff ) )+ toHexString( (byte) ( val & 0x00ff ) ); } if ( val > 0xffff && val <= 0xffffff ) { return toHexString( (byte) ( (val >>> 16 ) & 0x00ff ) )+toHexString( (byte) ( (val >>> 8 ) & 0x00ff ) )+toHexString( (byte) ( val & 0x00ff ) ); } if ( val <= 0xffff ) { return toHexString( (byte) ( (val >>> 8 ) & 0x00ff ) )+toHexString( (byte) ( val & 0x00ff ) ); } return "Value out-of-range: "+val; } public static String toHexString(long val) { return toHexString( (byte) ( (val >>> 24 ) & 0xff ) )+ toHexString( (byte) ( (val >>> 16 ) & 0xff ) )+ toHexString( (byte) ( (val >>> 8 ) & 0xff ) )+ toHexString( (byte) ( val & 0xff ) ); } public static String toHexString(byte val) { final int lo = ( val & 0x0f ); final int hi = ( val >>> 4) & 0x0f; return ""+HEX_CHARS[ hi ]+HEX_CHARS[ lo ]; } public static byte[] readBytes(IResource resource) throws IOException { final InputStream in = resource.createInputStream(); try { return readBytes( in ); } finally { IOUtils.closeQuietly( in ); } } public static byte[] readBytes(InputStream in) throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); int len = 0 ; final byte[] buffer = new byte[1024]; while ( ( len = in.read( buffer) ) > 0 ) { out.write( buffer ,0 , len ); } out.flush(); return out.toByteArray(); } public static String readSource(IResource resource) throws IOException { return new String( readBytes( resource ) ); } public static String readSource(InputStream in) throws IOException { return new String( readBytes( in ) ); } public static String readSource(ICompilationUnit unit) throws IOException { return readSource( unit.getResource().createInputStream() ); } public static String toPrettyString(String errorMessage, int errorOffset , String input) { return toPrettyString( errorMessage , errorOffset , new Scanner( input ) ); } public static String toPrettyString(String errorMessage, SourceLocation location , IScanner input) { return toPrettyString( errorMessage , location.getStartingOffset() , location , input ); } public static String toPrettyString(String errorMessage, int errorOffset , IScanner input) { return toPrettyString( errorMessage , errorOffset , null , input ); } public static String toPrettyString(String errorMessage, int errorOffset , ITextRegion location , IScanner input) { int oldOffset = input.currentParseIndex(); try { input.setCurrentParseIndex( errorOffset ); } catch(IllegalArgumentException e) { return "ERROR at offset "+errorOffset+": "+errorMessage; } try { StringBuilder context = new StringBuilder(); while ( ! input.eof() && input.peek() != '\n' ) { context.append( input.read() ); } final String loc; if ( location instanceof SourceLocation) { final SourceLocation srcLoc = (SourceLocation) location; loc = "Line "+ srcLoc.getLineNumber()+",column "+ srcLoc.getColumnNumber()+" ("+srcLoc.getOffset()+"): "; } else { loc = "index "+errorOffset+": "; } final String line1 = loc+context.toString(); final String indent = StringUtils.repeat(" ", loc.length() ); final String line2 = indent+"^ "+errorMessage; return line1+"\n"+line2; } finally { try { input.setCurrentParseIndex( oldOffset ); } catch(Exception e2) { /* swallow */ } } } public static void printCompilationErrors(ICompilationUnit unit,IResource resource,boolean printStackTraces) throws IOException { final String source = readSource( resource ); printCompilationErrors( unit , source , printStackTraces ); } public static void printCompilationErrors(ICompilationUnit unit,String source,boolean printStackTraces) { final List<IMarker> markers = unit.getMarkers( IMarker.TYPE_COMPILATION_ERROR , IMarker.TYPE_COMPILATION_WARNING , IMarker.TYPE_GENERIC_COMPILATION_ERROR ); printCompilationMarkers(unit,source,printStackTraces, markers ); } public static void printCompilationMarkers(ICompilationUnit unit,String source,boolean printStackTraces, final List<IMarker> errors ) { if ( errors.isEmpty() ) { return; } Collections.sort( errors ,new Comparator<IMarker>() { @Override public int compare(IMarker o1, IMarker o2) { if ( o1 instanceof CompilationMarker && o2 instanceof CompilationMarker ) { CompilationMarker err1 = (CompilationMarker) o1; CompilationMarker err2 = (CompilationMarker) o2; final ITextRegion loc1 = err1.getLocation(); final ITextRegion loc2 = err2.getLocation(); if ( loc1 != null && loc2 != null ) { return Integer.valueOf( err1.getLocation().getStartingOffset() ).compareTo( Integer.valueOf( err2.getLocation().getStartingOffset() ) ); } if ( loc1 != null ) { return -1; } else if ( loc2 != null ) { return 1; } return 0; } return 0; } }); for ( Iterator<IMarker> it = errors.iterator(); it.hasNext(); ) { final IMarker tmp = it.next(); if ( ! ( tmp instanceof CompilationMarker) ) { continue; } final CompilationMarker error=(CompilationMarker) tmp; final int errorOffset; ITextRegion range; if ( error.getLocation() != null ) { range = error.getLocation(); errorOffset= range.getStartingOffset(); } else { errorOffset=error.getErrorOffset(); if ( errorOffset != -1 ) { range = new TextRegion( errorOffset , 1 ); } else { range = null; } } int line = error.getLineNumber(); int column = error.getColumnNumber(); if ( column == -1 && ( error.getErrorOffset() != -1 && error.getLineStartOffset() != -1 ) ) { column = error.getErrorOffset() - error.getLineStartOffset()+1; } if ( (line == -1 || column == -1) & errorOffset != -1 ) { try { SourceLocation location = unit.getSourceLocation( range ); line = location.getLineNumber(); column = errorOffset - location.getLineStartOffset()+1; if ( column < 1 ) { column = -1; } } catch(Exception e) { // can't help it } } final boolean hasLocation = line != -1 && column != -1; final String severity = error.getSeverity() != null ? error.getSeverity().getLabel()+": " : ""; final String locationString; if ( hasLocation ) { locationString= severity+"line "+line+", column "+column+": "; } else if ( errorOffset != -1 ) { locationString=severity+"offset "+errorOffset+": "; } else { locationString=severity+"< unknown location >: "; } boolean hasSource=false; String sourceLine = null; if ( line != -1 || range != null ) { Line thisLine=null; Line nextLine=null; if ( line != -1 ) { try { thisLine = error.getCompilationUnit().getLineByNumber( line ); IScanner scanner = new Scanner( source ); scanner.setCurrentParseIndex( thisLine.getLineStartingOffset() ); while ( ! scanner.eof() && scanner.peek() != '\n' ) { scanner.read(); } nextLine = new Line( line+1 , scanner.currentParseIndex() ); } catch(Exception e) { // can't help it } if ( thisLine != null && nextLine != null ) { sourceLine = new TextRegion( thisLine.getLineStartingOffset() , nextLine.getLineStartingOffset() - thisLine.getLineStartingOffset() ).apply( source ); } else { sourceLine = range.apply( source ); column=1; } hasSource = true; } else { // range != null sourceLine = range.apply( source ); column=1; hasSource = true; } } if ( hasSource ) { sourceLine = sourceLine.replaceAll( Pattern.quote("\r\n") , "" ).replaceAll( Pattern.quote("\n") , "" ).replaceAll("\t" , " "); final String trimmedSourceLine = removeLeadingWhitespace( sourceLine ); if ( column != -1 && trimmedSourceLine.length() != sourceLine.length() ) { column -= ( sourceLine.length() - trimmedSourceLine.length() ); } sourceLine = trimmedSourceLine; } String firstLine; String secondLine=null; if ( hasLocation ) { if ( hasSource ) { firstLine=locationString+sourceLine+"\n"; secondLine=StringUtils.repeat(" " ,locationString.length())+StringUtils.repeat( " ", (column-1) )+"^ "+error.getMessage()+"\n"; } else { firstLine=locationString+error.getMessage(); } } else { firstLine="Unknown error: "+error.getMessage(); } System.err.print( firstLine ); if ( secondLine != null ) { System.err.print( secondLine ); } System.out.println(); if ( printStackTraces && error.getCause() != null ) { error.getCause().printStackTrace(); } } } public static String removeLeadingWhitespace(String input) { StringBuilder output = new StringBuilder(input); while ( output.length() > 0 && Character.isWhitespace( output.charAt( 0 ) ) ) { output.delete( 0 , 1 ); } return output.toString(); } public static String padRight(String input,int length) { final int delta = length - input.length(); final String result; if ( delta <= 0 ) { result = input; } else { result = input+StringUtils.repeat(" ",delta); } return result; } public static String padLeft(String input,int length) { final int delta = length - input.length(); final String result; if ( delta <= 0 ) { result = input; } else { result = StringUtils.repeat(" ",delta)+input; } return result; } public static String toBinaryString(int value,int padToLength) { return toBinaryString(value,padToLength,new int[0]); } public static String toBinaryString(int value,int padToLength,int... separatorsAtBits) { final StringBuilder result = new StringBuilder(); final Set<Integer> separators = new HashSet<Integer>(); if ( ! ArrayUtils.isEmpty( separatorsAtBits ) ) { for ( int bitPos : separatorsAtBits ) { separators.add( bitPos ); } } for ( int i = 15 ; i >= 0 ; i-- ) { if ( ( value & ( 1 << i ) ) != 0 ) { result.append("1"); } else { result.append("0"); } } final String s = result.toString(); if ( s.length() < padToLength ) { final int delta = padToLength - s.length(); return StringUtils.repeat("0" , delta )+s; } if ( ! separators.isEmpty() ) { final StringBuilder finalResult = new StringBuilder(); for ( int i = result.length() -1 ; i >= 0 ; i-- ) { finalResult.append( result.charAt( i ) ); final int bitOffset = result.length() -2-i; if ( separators.contains( bitOffset ) ) { finalResult.append(" "); } } return finalResult.toString(); } return s; } public static String toString(List<DisassembledLine> lines) { StringBuilder result = new StringBuilder(); final Iterator<DisassembledLine> it = lines.iterator(); while( it.hasNext() ) { final DisassembledLine line = it.next(); result.append( Misc.toHexString( line.getAddress().getValue() ) ).append(": ").append( line.getContents() ); if ( it.hasNext() ) { result.append("\n"); } } return result.toString(); } public static void copyResource(IResource source,IResource target) throws IOException { if ( source == null ) { throw new IllegalArgumentException("source must not be NULL."); } if ( target == null ) { throw new IllegalArgumentException("target must not be NULL."); } final InputStream in = source.createInputStream(); try { final OutputStream out = target.createOutputStream(false); try { IOUtils.copy(in , out ); } finally { IOUtils.closeQuietly( out ); } } finally { IOUtils.closeQuietly( in ); } } /** * Check whether a given file exists and is a directory, optionally * creating it. * * @param f * @param createIfMissing * @return <code>true</code> if the directory was missing and has been created * @throws FileNotFoundException thrown if the directory does not exist * @throws IOException thrown if creating the directory failed * @throws NoDirectoryException thrown if the file exists but is no directory */ public static boolean checkFileExistsAndIsDirectory(File f,boolean createIfMissing) throws FileNotFoundException,IOException,NoDirectoryException { if ( ! f.exists() ) { if ( createIfMissing ) { if ( f.mkdirs() ) { return true; } throw new IOException( "Failed to create missing directory "+f.getAbsolutePath() ); } throw new FileNotFoundException("Non-existant directory "+f.getAbsolutePath() ); } if ( ! f.isDirectory() ) { throw new IOException( f.getAbsolutePath()+" is no directory"); } return false; } public static File getUserHomeDirectory() { final String homeDirectory = System.getProperty("user.home"); if ( StringUtils.isBlank( homeDirectory ) ) { LOG.fatal("createDefaultConfiguration(): Failed to get user's home directory"); throw new RuntimeException("Failed to get user's home directory"); } return new File( homeDirectory ); } public static void writeResource(IResource resource,String s) throws IOException { final OutputStreamWriter writer = new OutputStreamWriter( resource.createOutputStream( false ) ); try { writer.write( s ); } finally { IOUtils.closeQuietly( writer ); } } public static void writeFile(File file,String s) throws IOException { writeFile( file , s.getBytes() ); } public static void writeFile(File file,byte[] data) throws IOException { final FileOutputStream out = new FileOutputStream(file); try { IOUtils.write( data , out ); } finally { IOUtils.closeQuietly( out ); } } public static void deleteRecursively(File file) throws IOException { deleteRecursively( file , null ); } /** * Visit directory tree in post-order, deleting files as we go along. * * @param file * @param visitor <code>null</code> or visitor that is invoked on each file/directory BEFORE * it get's deleted. If the visitor returns <code>false</code> , the file/directory will NOT be deleted. * @throws IOException */ public static void deleteRecursively(File file,final IFileVisitor visitor) throws IOException { if ( ! file.exists() ) { return; } final IFileVisitor deletingVisitor = new IFileVisitor() { @Override public boolean visit(File file) throws IOException { if ( visitor == null || visitor.visit( file ) ) { file.delete(); } return true; } }; visitDirectoryTreePostOrder( file , deletingVisitor ); } public interface IFileVisitor { public boolean visit(File file) throws IOException; } public static boolean visitDirectoryTreePostOrder(File currentDir,IFileVisitor visitor) throws IOException { if ( currentDir.isDirectory() ) { for ( File f : currentDir.listFiles() ) { if ( ! visitDirectoryTreePostOrder( f , visitor ) ) { return false; } } } final boolean cont = visitor.visit( currentDir ); if ( ! cont ) { return false; } return true; } public static boolean visitDirectoryTreeInOrder(File currentDir,IFileVisitor visitor) throws IOException { final boolean cont = visitor.visit( currentDir ); if ( ! cont ) { return false; } if ( currentDir.isDirectory() ) { for ( File f : currentDir.listFiles() ) { if ( ! visitDirectoryTreeInOrder( f , visitor ) ) { return false; } } } return true; } public static String calcHash(String data) { final MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } final byte[] result = digest.digest( data.getBytes() ); return toHexString( result ); } public static <T> T[] subarray(T[] array , int beginIndex , int endIndex) { final Class<?> componentType = array.getClass().getComponentType(); @SuppressWarnings("unchecked") final T[] result = (T[]) Array.newInstance( componentType , endIndex - beginIndex ); int offset = 0; for ( int i = beginIndex ; i < endIndex ; i++ , offset++) { result[offset] = array[i]; } return result; } public static long parseHexString(String s) throws NumberFormatException { String trimmed = s.toLowerCase().trim(); if ( trimmed.startsWith("0x") ) { trimmed = trimmed.substring( 2 , trimmed.length() ); } long result = 0; for ( int i = 0 ; i < trimmed.length() ; i++ ) { result = result << 4; int nibble = -1; for ( int j = 0 ; j < HEX_CHARS.length ; j++ ) { if ( HEX_CHARS[j] == trimmed.charAt( i ) ) { nibble = j; break; } } if ( nibble < 0 ) { throw new NumberFormatException("Not a valid hex string: '"+s+"'"); } result = result | nibble; } return result; } }