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