de.codesourcery.jasm16.utils.Misc.java Source code

Java tutorial

Introduction

Here is the source code for de.codesourcery.jasm16.utils.Misc.java

Source

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